]> git.lyx.org Git - lyx.git/blob - src/Paragraph.cpp
adjust
[lyx.git] / src / Paragraph.cpp
1 /**
2  * \file Paragraph.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author Angus Leeming
10  * \author John Levon
11  * \author André Pönitz
12  * \author Dekel Tsur
13  * \author Jürgen Vigna
14  *
15  * Full author contact details are available in file CREDITS.
16  */
17
18 #include <config.h>
19
20 #include "Paragraph.h"
21
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "Counters.h"
25 #include "Encoding.h"
26 #include "debug.h"
27 #include "gettext.h"
28 #include "Language.h"
29 #include "LaTeXFeatures.h"
30 #include "Color.h"
31 #include "Layout.h"
32 #include "Length.h"
33 #include "Font.h"
34 #include "LyXRC.h"
35 #include "Messages.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "paragraph_funcs.h"
39 #include "ParagraphParameters.h"
40 #include "sgml.h"
41 #include "TexRow.h"
42 #include "VSpace.h"
43
44 #include "frontends/alert.h"
45 #include "frontends/FontMetrics.h"
46
47 #include "insets/InsetBibitem.h"
48 #include "insets/InsetOptArg.h"
49
50 #include "support/lstrings.h"
51 #include "support/textutils.h"
52 #include "support/convert.h"
53 #include "support/unicode.h"
54
55 #include <boost/bind.hpp>
56 #include <boost/next_prior.hpp>
57
58 #include <algorithm>
59 #include <sstream>
60
61 using std::distance;
62 using std::endl;
63 using std::string;
64 using std::ostream;
65
66 namespace lyx {
67
68 using support::contains;
69 using support::prefixIs;
70 using support::suffixIs;
71 using support::rsplit;
72 using support::rtrim;
73
74
75 /////////////////////////////////////////////////////////////////////
76 //
77 // Paragraph::Pimpl
78 //
79 /////////////////////////////////////////////////////////////////////
80
81 class Encoding;
82 class Layout;
83
84
85 class Paragraph::Pimpl {
86 public:
87         ///
88         Pimpl(Paragraph * owner);
89         /// "Copy constructor"
90         Pimpl(Pimpl const &, Paragraph * owner);
91
92         //
93         // Change tracking
94         //
95         /// look up change at given pos
96         Change const & lookupChange(pos_type pos) const;
97         /// is there a change within the given range ?
98         bool isChanged(pos_type start, pos_type end) const;
99         /// will the paragraph be physically merged with the next
100         /// one if the imaginary end-of-par character is logically deleted?
101         bool isMergedOnEndOfParDeletion(bool trackChanges) const;
102         /// set change for the entire par
103         void setChange(Change const & change);
104         /// set change at given pos
105         void setChange(pos_type pos, Change const & change);
106         /// accept changes within the given range
107         void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
108         /// reject changes within the given range
109         void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
110
111         ///
112         value_type getChar(pos_type pos) const;
113         ///
114         void insertChar(pos_type pos, value_type c, Change const & change);
115         ///
116         void insertInset(pos_type pos, Inset * inset, Change const & change);
117         /// (logically) erase the char at pos; return true if it was actually erased
118         bool eraseChar(pos_type pos, bool trackChanges);
119         /// (logically) erase the given range; return the number of chars actually erased
120         int eraseChars(pos_type start, pos_type end, bool trackChanges);
121         ///
122         Inset * inset_owner;
123
124         /** A font entry covers a range of positions. Notice that the
125             entries in the list are inserted in random order.
126             I don't think it's worth the effort to implement a more effective
127             datastructure, because the number of different fonts in a paragraph
128             is limited. (Asger)
129             Nevertheless, I decided to store fontlist using a sorted vector:
130             fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
131             pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
132             and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
133             (font_1 covers the chars 0,...,pos_1) (Dekel)
134         */
135         class FontTable  {
136         public:
137                 ///
138                 FontTable(pos_type p, Font const & f)
139                         : pos_(p), font_(f)
140                 {}
141                 ///
142                 pos_type pos() const { return pos_; }
143                 ///
144                 void pos(pos_type p) { pos_ = p; }
145                 ///
146                 Font const & font() const { return font_; }
147                 ///
148                 void font(Font const & f) { font_ = f;}
149         private:
150                 /// End position of paragraph this font attribute covers
151                 pos_type pos_;
152                 /** Font. Interpretation of the font values:
153                     If a value is Font::INHERIT_*, it means that the font
154                     attribute is inherited from either the layout of this
155                     paragraph or, in the case of nested paragraphs, from the
156                     layout in the environment one level up until completely
157                     resolved.
158                     The values Font::IGNORE_* and Font::TOGGLE are NOT
159                     allowed in these font tables.
160                 */
161                 Font font_;
162         };
163         ///
164         friend class matchFT;
165         ///
166         class matchFT {
167         public:
168                 /// used by lower_bound and upper_bound
169                 int operator()(FontTable const & a, FontTable const & b) const {
170                         return a.pos() < b.pos();
171                 }
172         };
173
174         ///
175         typedef std::vector<FontTable> FontList;
176         ///
177         FontList fontlist;
178
179         /// Output the surrogate pair formed by \p c and \p next to \p os.
180         /// \return the number of characters written.
181         int latexSurrogatePair(odocstream & os, value_type c, value_type next,
182                                Encoding const &);
183         /// Output a space in appropriate formatting (or a surrogate pair
184         /// if the next character is a combining character).
185         /// \return whether a surrogate pair was output.
186         bool simpleTeXBlanks(Encoding const &,
187                              odocstream &, TexRow & texrow,
188                              pos_type & i,
189                              unsigned int & column,
190                              Font const & font,
191                              Layout const & style);
192         /// Output consecutive known unicode chars, belonging to the same
193         /// language as specified by \p preamble, to \p os starting from \p c.
194         /// \return the number of characters written.
195         int knownLangChars(odocstream & os, value_type c, string & preamble,
196                            Change &, Encoding const &, pos_type &);
197         ///
198         void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
199                                    odocstream &,
200                                    TexRow & texrow, OutputParams &,
201                                    Font & running_font,
202                                    Font & basefont,
203                                    Font const & outerfont,
204                                    bool & open_font,
205                                    Change & running_change,
206                                    Layout const & style,
207                                    pos_type & i,
208                                    unsigned int & column, value_type const c);
209
210         ///
211         void validate(LaTeXFeatures & features,
212                       Layout const & layout) const;
213
214         ///
215         unsigned int id_;
216         ///
217         static unsigned int paragraph_id;
218         ///
219         ParagraphParameters params;
220
221 //private:
222         ///
223         pos_type size() const { return owner_->size(); }
224         /// match a string against a particular point in the paragraph
225         bool isTextAt(std::string const & str, pos_type pos) const;
226
227         /// for recording and looking up changes
228         Changes changes_;
229
230         /// Who owns us?
231         Paragraph * owner_;
232 };
233
234
235
236
237 using std::endl;
238 using std::upper_bound;
239 using std::lower_bound;
240 using std::string;
241
242
243 // Initialization of the counter for the paragraph id's,
244 unsigned int Paragraph::Pimpl::paragraph_id = 0;
245
246 namespace {
247
248 struct special_phrase {
249         string phrase;
250         docstring macro;
251         bool builtin;
252 };
253
254 special_phrase const special_phrases[] = {
255         { "LyX", from_ascii("\\LyX{}"), false },
256         { "TeX", from_ascii("\\TeX{}"), true },
257         { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
258         { "LaTeX", from_ascii("\\LaTeX{}"), true },
259 };
260
261 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
262
263 } // namespace anon
264
265
266 Paragraph::Pimpl::Pimpl(Paragraph * owner)
267         : owner_(owner)
268 {
269         inset_owner = 0;
270         id_ = paragraph_id++;
271 }
272
273
274 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
275         : params(p.params), changes_(p.changes_), owner_(owner)
276 {
277         inset_owner = p.inset_owner;
278         fontlist = p.fontlist;
279         id_ = paragraph_id++;
280 }
281
282
283 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
284 {
285         BOOST_ASSERT(start >= 0 && start <= size());
286         BOOST_ASSERT(end > start && end <= size() + 1);
287
288         return changes_.isChanged(start, end);
289 }
290
291
292 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
293         // keep the logic here in sync with the logic of eraseChars()
294
295         if (!trackChanges) {
296                 return true;
297         }
298
299         Change change = changes_.lookup(size());
300
301         return change.type == Change::INSERTED && change.author == 0;
302 }
303
304
305 void Paragraph::Pimpl::setChange(Change const & change)
306 {
307         // beware of the imaginary end-of-par character!
308         changes_.set(change, 0, size() + 1);
309
310         /*
311          * Propagate the change recursively - but not in case of DELETED!
312          *
313          * Imagine that your co-author makes changes in an existing inset. He
314          * sends your document to you and you come to the conclusion that the
315          * inset should go completely. If you erase it, LyX must not delete all
316          * text within the inset. Otherwise, the change tracked insertions of
317          * your co-author get lost and there is no way to restore them later.
318          *
319          * Conclusion: An inset's content should remain untouched if you delete it
320          */
321
322         if (change.type != Change::DELETED) {
323                 for (pos_type pos = 0; pos < size(); ++pos) {
324                         if (owner_->isInset(pos)) {
325                                 owner_->getInset(pos)->setChange(change);
326                         }
327                 }
328         }
329 }
330
331
332 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
333 {
334         BOOST_ASSERT(pos >= 0 && pos <= size());
335
336         changes_.set(change, pos);
337
338         // see comment in setChange(Change const &) above
339
340         if (change.type != Change::DELETED &&
341             pos < size() && owner_->isInset(pos)) {
342                 owner_->getInset(pos)->setChange(change);
343         }
344 }
345
346
347 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
348 {
349         BOOST_ASSERT(pos >= 0 && pos <= size());
350
351         return changes_.lookup(pos);
352 }
353
354
355 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
356 {
357         BOOST_ASSERT(start >= 0 && start <= size());
358         BOOST_ASSERT(end > start && end <= size() + 1);
359
360         for (pos_type pos = start; pos < end; ++pos) {
361                 switch (lookupChange(pos).type) {
362                         case Change::UNCHANGED:
363                                 // accept changes in nested inset
364                                 if (pos < size() && owner_->isInset(pos)) {
365                                         owner_->getInset(pos)->acceptChanges(bparams);
366                                 }
367
368                                 break;
369
370                         case Change::INSERTED:
371                                 changes_.set(Change(Change::UNCHANGED), pos);
372                                 // also accept changes in nested inset
373                                 if (pos < size() && owner_->isInset(pos)) {
374                                         owner_->getInset(pos)->acceptChanges(bparams);
375                                 }
376                                 break;
377
378                         case Change::DELETED:
379                                 // Suppress access to non-existent
380                                 // "end-of-paragraph char"
381                                 if (pos < size()) {
382                                         eraseChar(pos, false);
383                                         --end;
384                                         --pos;
385                                 }
386                                 break;
387                 }
388
389         }
390 }
391
392
393 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
394 {
395         BOOST_ASSERT(start >= 0 && start <= size());
396         BOOST_ASSERT(end > start && end <= size() + 1);
397
398         for (pos_type pos = start; pos < end; ++pos) {
399                 switch (lookupChange(pos).type) {
400                         case Change::UNCHANGED:
401                                 // reject changes in nested inset
402                                 if (pos < size() && owner_->isInset(pos)) {
403                                         owner_->getInset(pos)->rejectChanges(bparams);
404                                 }
405                                 break;
406
407                         case Change::INSERTED:
408                                 // Suppress access to non-existent
409                                 // "end-of-paragraph char"
410                                 if (pos < size()) {
411                                         eraseChar(pos, false);
412                                         --end;
413                                         --pos;
414                                 }
415                                 break;
416
417                         case Change::DELETED:
418                                 changes_.set(Change(Change::UNCHANGED), pos);
419
420                                 // Do NOT reject changes within a deleted inset!
421                                 // There may be insertions of a co-author inside of it!
422
423                                 break;
424                 }
425         }
426 }
427
428
429 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
430 {
431         BOOST_ASSERT(pos >= 0 && pos <= size());
432
433         return owner_->getChar(pos);
434 }
435
436
437 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
438 {
439         BOOST_ASSERT(pos >= 0 && pos <= size());
440
441         // track change
442         changes_.insert(change, pos);
443
444         // This is actually very common when parsing buffers (and
445         // maybe inserting ascii text)
446         if (pos == size()) {
447                 // when appending characters, no need to update tables
448                 owner_->text_.push_back(c);
449                 return;
450         }
451
452         owner_->text_.insert(owner_->text_.begin() + pos, c);
453
454         // Update the font table.
455         FontTable search_font(pos, Font());
456         for (FontList::iterator it
457               = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
458              it != fontlist.end(); ++it)
459         {
460                 it->pos(it->pos() + 1);
461         }
462
463         // Update the insets
464         owner_->insetlist.increasePosAfterPos(pos);
465 }
466
467
468 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
469                                    Change const & change)
470 {
471         BOOST_ASSERT(inset);
472         BOOST_ASSERT(pos >= 0 && pos <= size());
473
474         insertChar(pos, META_INSET, change);
475         BOOST_ASSERT(owner_->text_[pos] == META_INSET);
476
477         // Add a new entry in the insetlist.
478         owner_->insetlist.insert(inset, pos);
479 }
480
481
482 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
483 {
484         BOOST_ASSERT(pos >= 0 && pos <= size());
485
486         // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
487
488         if (trackChanges) {
489                 Change change = changes_.lookup(pos);
490
491                 // set the character to DELETED if
492                 //  a) it was previously unchanged or
493                 //  b) it was inserted by a co-author
494
495                 if (change.type == Change::UNCHANGED ||
496                     (change.type == Change::INSERTED && change.author != 0)) {
497                         setChange(pos, Change(Change::DELETED));
498                         return false;
499                 }
500
501                 if (change.type == Change::DELETED)
502                         return false;
503         }
504
505         // Don't physically access the imaginary end-of-paragraph character.
506         // eraseChar() can only mark it as DELETED. A physical deletion of
507         // end-of-par must be handled externally.
508         if (pos == size()) {
509                 return false;
510         }
511
512         // track change
513         changes_.erase(pos);
514
515         // if it is an inset, delete the inset entry
516         if (owner_->text_[pos] == Paragraph::META_INSET) {
517                 owner_->insetlist.erase(pos);
518         }
519
520         owner_->text_.erase(owner_->text_.begin() + pos);
521
522         // Erase entries in the tables.
523         FontTable search_font(pos, Font());
524
525         FontList::iterator it =
526                 lower_bound(fontlist.begin(),
527                             fontlist.end(),
528                             search_font, matchFT());
529         if (it != fontlist.end() && it->pos() == pos &&
530             (pos == 0 ||
531              (it != fontlist.begin()
532               && boost::prior(it)->pos() == pos - 1))) {
533                 // If it is a multi-character font
534                 // entry, we just make it smaller
535                 // (see update below), otherwise we
536                 // should delete it.
537                 unsigned int const i = it - fontlist.begin();
538                 fontlist.erase(fontlist.begin() + i);
539                 it = fontlist.begin() + i;
540                 if (i > 0 && i < fontlist.size() &&
541                     fontlist[i - 1].font() == fontlist[i].font()) {
542                         fontlist.erase(fontlist.begin() + i - 1);
543                         it = fontlist.begin() + i - 1;
544                 }
545         }
546
547         // Update all other entries
548         FontList::iterator fend = fontlist.end();
549         for (; it != fend; ++it)
550                 it->pos(it->pos() - 1);
551
552         // Update the insetlist
553         owner_->insetlist.decreasePosAfterPos(pos);
554
555         return true;
556 }
557
558
559 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
560 {
561         BOOST_ASSERT(start >= 0 && start <= size());
562         BOOST_ASSERT(end >= start && end <= size() + 1);
563
564         pos_type i = start;
565         for (pos_type count = end - start; count; --count) {
566                 if (!eraseChar(i, trackChanges))
567                         ++i;
568         }
569         return end - i;
570 }
571
572
573 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
574                 value_type next, Encoding const & encoding)
575 {
576         // Writing next here may circumvent a possible font change between
577         // c and next. Since next is only output if it forms a surrogate pair
578         // with c we can ignore this:
579         // A font change inside a surrogate pair does not make sense and is
580         // hopefully impossible to input.
581         // FIXME: change tracking
582         // Is this correct WRT change tracking?
583         docstring const latex1 = encoding.latexChar(next);
584         docstring const latex2 = encoding.latexChar(c);
585         os << latex1 << '{' << latex2 << '}';
586         return latex1.length() + latex2.length() + 2;
587 }
588
589
590 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
591                                        odocstream & os, TexRow & texrow,
592                                        pos_type & i,
593                                        unsigned int & column,
594                                        Font const & font,
595                                        Layout const & style)
596 {
597         if (style.pass_thru)
598                 return false;
599
600         if (i + 1 < size()) {
601                 char_type next = getChar(i + 1);
602                 if (Encodings::isCombiningChar(next)) {
603                         // This space has an accent, so we must always output it.
604                         column += latexSurrogatePair(os, ' ', next, encoding) - 1;
605                         ++i;
606                         return true;
607                 }
608         }
609
610         if (lyxrc.plaintext_linelen > 0
611             && column > lyxrc.plaintext_linelen
612             && i
613             && getChar(i - 1) != ' '
614             && (i + 1 < size())
615             // same in FreeSpacing mode
616             && !owner_->isFreeSpacing()
617             // In typewriter mode, we want to avoid
618             // ! . ? : at the end of a line
619             && !(font.family() == Font::TYPEWRITER_FAMILY
620                  && (getChar(i - 1) == '.'
621                      || getChar(i - 1) == '?'
622                      || getChar(i - 1) == ':'
623                      || getChar(i - 1) == '!'))) {
624                 os << '\n';
625                 texrow.newline();
626                 texrow.start(owner_->id(), i + 1);
627                 column = 0;
628         } else if (style.free_spacing) {
629                 os << '~';
630         } else {
631                 os << ' ';
632         }
633         return false;
634 }
635
636
637 int Paragraph::Pimpl::knownLangChars(odocstream & os,
638                                      value_type c,
639                                      string & preamble,
640                                      Change & runningChange,
641                                      Encoding const & encoding,
642                                      pos_type & i)
643 {
644         // When the character is marked by the proper language, we simply
645         // get its code point in some encoding, otherwise we get the
646         // translation specified in the unicodesymbols file, which is
647         // something like "\textLANG{<spec>}". So, we have to retain
648         // "\textLANG{<spec>" for the first char but only "<spec>" for
649         // all subsequent chars.
650         docstring const latex1 = rtrim(encoding.latexChar(c), "}");
651         int length = latex1.length();
652         os << latex1;
653         while (i + 1 < size()) {
654                 char_type next = getChar(i + 1);
655                 // Stop here if next character belongs to another
656                 // language or there is a change tracking status.
657                 if (!Encodings::isKnownLangChar(next, preamble) ||
658                     runningChange != lookupChange(i + 1))
659                         break;
660                 Font prev_font;
661                 bool found = false;
662                 FontList::const_iterator cit = fontlist.begin();
663                 FontList::const_iterator end = fontlist.end();
664                 for (; cit != end; ++cit) {
665                         if (cit->pos() >= i && !found) {
666                                 prev_font = cit->font();
667                                 found = true;
668                         }
669                         if (cit->pos() >= i + 1)
670                                 break;
671                 }
672                 // Stop here if there is a font attribute change.
673                 if (found && cit != end && prev_font != cit->font())
674                         break;
675                 docstring const latex = rtrim(encoding.latexChar(next), "}");
676                 docstring::size_type const j =
677                                         latex.find_first_of(from_ascii("{"));
678                 if (j == docstring::npos) {
679                         os << latex;
680                         length += latex.length();
681                 } else {
682                         os << latex.substr(j + 1);
683                         length += latex.substr(j + 1).length();
684                 }
685                 ++i;
686         }
687         // When the proper language is set, we are simply passed a code
688         // point, so we should not try to close the \textLANG command.
689         if (prefixIs(latex1, from_ascii("\\" + preamble))) {
690                 os << '}';
691                 ++length;
692         }
693         return length;
694 }
695
696
697 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
698 {
699         pos_type const len = str.length();
700
701         // is the paragraph large enough?
702         if (pos + len > size())
703                 return false;
704
705         // does the wanted text start at point?
706         for (string::size_type i = 0; i < str.length(); ++i) {
707                 // Caution: direct comparison of characters works only
708                 // because str is pure ASCII.
709                 if (str[i] != owner_->text_[pos + i])
710                         return false;
711         }
712
713         // is there a font change in middle of the word?
714         FontList::const_iterator cit = fontlist.begin();
715         FontList::const_iterator end = fontlist.end();
716         for (; cit != end; ++cit) {
717                 if (cit->pos() >= pos)
718                         break;
719         }
720         if (cit != end && pos + len - 1 > cit->pos())
721                 return false;
722
723         return true;
724 }
725
726
727 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
728                                              BufferParams const & bparams,
729                                              odocstream & os,
730                                              TexRow & texrow,
731                                              OutputParams & runparams,
732                                              Font & running_font,
733                                              Font & basefont,
734                                              Font const & outerfont,
735                                              bool & open_font,
736                                              Change & running_change,
737                                              Layout const & style,
738                                              pos_type & i,
739                                              unsigned int & column,
740                                              value_type const c)
741 {
742         if (style.pass_thru) {
743                 if (c != Paragraph::META_INSET) {
744                         if (c != '\0')
745                                 // FIXME UNICODE: This can fail if c cannot
746                                 // be encoded in the current encoding.
747                                 os.put(c);
748                 } else
749                         owner_->getInset(i)->plaintext(buf, os, runparams);
750                 return;
751         }
752
753         // Two major modes:  LaTeX or plain
754         // Handle here those cases common to both modes
755         // and then split to handle the two modes separately.
756         switch (c) {
757         case Paragraph::META_INSET: {
758                 Inset * inset = owner_->getInset(i);
759
760                 // FIXME: remove this check
761                 if (!inset)
762                         break;
763
764                 // FIXME: move this to InsetNewline::latex
765                 if (inset->lyxCode() == NEWLINE_CODE) {
766                         // newlines are handled differently here than
767                         // the default in simpleTeXSpecialChars().
768                         if (!style.newline_allowed) {
769                                 os << '\n';
770                         } else {
771                                 if (open_font) {
772                                         column += running_font.latexWriteEndChanges(
773                                                 os, bparams, runparams,
774                                                 basefont, basefont);
775                                         open_font = false;
776                                 }
777
778                                 if (running_font.family() == Font::TYPEWRITER_FAMILY)
779                                         os << '~';
780
781                                 basefont = owner_->getLayoutFont(bparams, outerfont);
782                                 running_font = basefont;
783
784                                 if (runparams.moving_arg)
785                                         os << "\\protect ";
786
787                                 os << "\\\\\n";
788                         }
789                         texrow.newline();
790                         texrow.start(owner_->id(), i + 1);
791                         column = 0;
792                         break;
793                 }
794
795                 if (lookupChange(i).type == Change::DELETED) {
796                         if( ++runparams.inDeletedInset == 1)
797                                 runparams.changeOfDeletedInset = lookupChange(i);
798                 }
799
800                 if (inset->canTrackChanges()) {
801                         column += Changes::latexMarkChange(os, bparams, running_change,
802                                 Change(Change::UNCHANGED));
803                         running_change = Change(Change::UNCHANGED);
804                 }
805
806                 bool close = false;
807                 odocstream::pos_type const len = os.tellp();
808
809                 if ((inset->lyxCode() == GRAPHICS_CODE
810                      || inset->lyxCode() == MATH_CODE
811                      || inset->lyxCode() == HYPERLINK_CODE)
812                     && running_font.isRightToLeft()) {
813                         if (running_font.language()->lang() == "farsi")
814                                 os << "\\beginL{}";
815                         else
816                                 os << "\\L{";
817                         close = true;
818                 }
819
820 // FIXME: Bug: we can have an empty font change here!
821 // if there has just been a font change, we are going to close it
822 // right now, which means stupid latex code like \textsf{}. AFAIK,
823 // this does not harm dvi output. A minor bug, thus (JMarc)
824                 // Some insets cannot be inside a font change command.
825                 // However, even such insets *can* be placed in \L or \R
826                 // or their equivalents (for RTL language switches), so we don't
827                 // close the language in those cases.
828                 // ArabTeX, though, cannot handle this special behavior, it seems.
829                 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
830                                            running_font.language()->lang() == "arabic_arabtex";
831                 if (open_font && inset->noFontChange()) {
832                         bool closeLanguage = arabtex ||
833                                 basefont.isRightToLeft() == running_font.isRightToLeft();
834                         unsigned int count = running_font.latexWriteEndChanges(
835                                         os, bparams, runparams,
836                                                 basefont, basefont, closeLanguage);
837                         column += count;
838                         // if any font properties were closed, update the running_font, 
839                         // making sure, however, to leave the language as it was
840                         if (count > 0) {
841                                 // FIXME: probably a better way to keep track of the old 
842                                 // language, than copying the entire font?
843                                 Font const copy_font(running_font);
844                                 basefont = owner_->getLayoutFont(bparams, outerfont);
845                                 running_font = basefont;
846                                 if (!closeLanguage)
847                                         running_font.setLanguage(copy_font.language());
848                                 // leave font open if language is still open
849                                 open_font = (running_font.language() == basefont.language());
850                                 if (closeLanguage)
851                                         runparams.local_font = &basefont;
852                         }
853                 }
854
855                 int tmp = inset->latex(buf, os, runparams);
856
857                 if (close) {
858                         if (running_font.language()->lang() == "farsi")
859                                 os << "\\endL{}";
860                         else
861                                 os << '}';
862                 }
863
864                 if (tmp) {
865                         for (int j = 0; j < tmp; ++j) {
866                                 texrow.newline();
867                         }
868                         texrow.start(owner_->id(), i + 1);
869                         column = 0;
870                 } else {
871                         column += os.tellp() - len;
872                 }
873
874                 if (lookupChange(i).type == Change::DELETED) {
875                         --runparams.inDeletedInset;
876                 }
877         }
878         break;
879
880         default:
881                 // And now for the special cases within each mode
882
883                 switch (c) {
884                 case '\\':
885                         os << "\\textbackslash{}";
886                         column += 15;
887                         break;
888
889                 case '|': case '<': case '>':
890                         // In T1 encoding, these characters exist
891                         if (lyxrc.fontenc == "T1") {
892                                 os.put(c);
893                                 //... but we should avoid ligatures
894                                 if ((c == '>' || c == '<')
895                                     && i <= size() - 2
896                                     && getChar(i + 1) == c) {
897                                         //os << "\\textcompwordmark{}";
898                                         //column += 19;
899                                         // Jean-Marc, have a look at
900                                         // this. I think this works
901                                         // equally well:
902                                         os << "\\,{}";
903                                         // Lgb
904                                         column += 3;
905                                 }
906                                 break;
907                         }
908                         // Typewriter font also has them
909                         if (running_font.family() == Font::TYPEWRITER_FAMILY) {
910                                 os.put(c);
911                                 break;
912                         }
913                         // Otherwise, we use what LaTeX
914                         // provides us.
915                         switch (c) {
916                         case '<':
917                                 os << "\\textless{}";
918                                 column += 10;
919                                 break;
920                         case '>':
921                                 os << "\\textgreater{}";
922                                 column += 13;
923                                 break;
924                         case '|':
925                                 os << "\\textbar{}";
926                                 column += 9;
927                                 break;
928                         }
929                         break;
930
931                 case '-': // "--" in Typewriter mode -> "-{}-"
932                         if (i <= size() - 2 &&
933                             getChar(i + 1) == '-' &&
934                             running_font.family() == Font::TYPEWRITER_FAMILY) {
935                                 os << "-{}";
936                                 column += 2;
937                         } else {
938                                 os << '-';
939                         }
940                         break;
941
942                 case '\"':
943                         os << "\\char`\\\"{}";
944                         column += 9;
945                         break;
946
947                 case '$': case '&':
948                 case '%': case '#': case '{':
949                 case '}': case '_':
950                         os << '\\';
951                         os.put(c);
952                         column += 1;
953                         break;
954
955                 case '~':
956                         os << "\\textasciitilde{}";
957                         column += 16;
958                         break;
959
960                 case '^':
961                         os << "\\textasciicircum{}";
962                         column += 17;
963                         break;
964
965                 case '*': case '[':
966                         // avoid being mistaken for optional arguments
967                         os << '{';
968                         os.put(c);
969                         os << '}';
970                         column += 2;
971                         break;
972
973                 case ' ':
974                         // Blanks are printed before font switching.
975                         // Sure? I am not! (try nice-latex)
976                         // I am sure it's correct. LyX might be smarter
977                         // in the future, but for now, nothing wrong is
978                         // written. (Asger)
979                         break;
980
981                 default:
982
983                         // I assume this is hack treating typewriter as verbatim
984                         // FIXME UNICODE: This can fail if c cannot be encoded
985                         // in the current encoding.
986                         if (running_font.family() == Font::TYPEWRITER_FAMILY) {
987                                 if (c != '\0') {
988                                         os.put(c);
989                                 }
990                                 break;
991                         }
992
993                         // LyX, LaTeX etc.
994
995                         // FIXME: if we have "LaTeX" with a font
996                         // change in the middle (before the 'T', then
997                         // the "TeX" part is still special cased.
998                         // Really we should only operate this on
999                         // "words" for some definition of word
1000
1001                         size_t pnr = 0;
1002
1003                         for (; pnr < phrases_nr; ++pnr) {
1004                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
1005                                         os << special_phrases[pnr].macro;
1006                                         i += special_phrases[pnr].phrase.length() - 1;
1007                                         column += special_phrases[pnr].macro.length() - 1;
1008                                         break;
1009                                 }
1010                         }
1011
1012                         if (pnr == phrases_nr && c != '\0') {
1013                                 Encoding const & encoding = *(runparams.encoding);
1014                                 if (i + 1 < size()) {
1015                                         char_type next = getChar(i + 1);
1016                                         if (Encodings::isCombiningChar(next)) {
1017                                                 column += latexSurrogatePair(os, c, next, encoding) - 1;
1018                                                 ++i;
1019                                                 break;
1020                                         }
1021                                 }
1022                                 string preamble;
1023                                 if (Encodings::isKnownLangChar(c, preamble)) {
1024                                         column +=
1025                                                 knownLangChars(os, c, preamble,
1026                                                         running_change,
1027                                                         encoding, i) - 1;
1028                                         break;
1029                                 }
1030                                 docstring const latex = encoding.latexChar(c);
1031                                 if (latex.length() > 1 &&
1032                                     latex[latex.length() - 1] != '}') {
1033                                         // Prevent eating of a following
1034                                         // space or command corruption by
1035                                         // following characters
1036                                         column += latex.length() + 1;
1037                                         os << latex << "{}";
1038                                 } else {
1039                                         column += latex.length() - 1;
1040                                         os << latex;
1041                                 }
1042                         }
1043                         break;
1044                 }
1045         }
1046 }
1047
1048
1049 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
1050                                 Layout const & layout) const
1051 {
1052         BufferParams const & bparams = features.bufferParams();
1053
1054         // check the params.
1055         if (!params.spacing().isDefault())
1056                 features.require("setspace");
1057
1058         // then the layouts
1059         features.useLayout(layout.name());
1060
1061         // then the fonts
1062         Language const * doc_language = bparams.language;
1063
1064         FontList::const_iterator fcit = fontlist.begin();
1065         FontList::const_iterator fend = fontlist.end();
1066         for (; fcit != fend; ++fcit) {
1067                 if (fcit->font().noun() == Font::ON) {
1068                         LYXERR(Debug::LATEX) << "font.noun: "
1069                                              << fcit->font().noun()
1070                                              << endl;
1071                         features.require("noun");
1072                         LYXERR(Debug::LATEX) << "Noun enabled. Font: "
1073                                              << to_utf8(fcit->font().stateText(0))
1074                                              << endl;
1075                 }
1076                 switch (fcit->font().color()) {
1077                 case Color::none:
1078                 case Color::inherit:
1079                 case Color::ignore:
1080                         // probably we should put here all interface colors used for
1081                         // font displaying! For now I just add this ones I know of (Jug)
1082                 case Color::latex:
1083                 case Color::note:
1084                         break;
1085                 default:
1086                         features.require("color");
1087                         LYXERR(Debug::LATEX) << "Color enabled. Font: "
1088                                              << to_utf8(fcit->font().stateText(0))
1089                                              << endl;
1090                 }
1091
1092                 Language const * language = fcit->font().language();
1093                 if (language->babel() != doc_language->babel() &&
1094                     language != ignore_language &&
1095                     language != latex_language)
1096                 {
1097                         features.useLanguage(language);
1098                         LYXERR(Debug::LATEX) << "Found language "
1099                                              << language->lang() << endl;
1100                 }
1101         }
1102
1103         if (!params.leftIndent().zero())
1104                 features.require("ParagraphLeftIndent");
1105
1106         // then the insets
1107         InsetList::const_iterator icit = owner_->insetlist.begin();
1108         InsetList::const_iterator iend = owner_->insetlist.end();
1109         for (; icit != iend; ++icit) {
1110                 if (icit->inset) {
1111                         icit->inset->validate(features);
1112                         if (layout.needprotect &&
1113                             icit->inset->lyxCode() == FOOT_CODE)
1114                                 features.require("NeedLyXFootnoteCode");
1115                 }
1116         }
1117
1118         // then the contents
1119         for (pos_type i = 0; i < size() ; ++i) {
1120                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1121                         if (!special_phrases[pnr].builtin
1122                             && isTextAt(special_phrases[pnr].phrase, i)) {
1123                                 features.require(special_phrases[pnr].phrase);
1124                                 break;
1125                         }
1126                 }
1127                 Encodings::validate(getChar(i), features);
1128         }
1129 }
1130
1131
1132 } // namespace lyx
1133
1134
1135 /////////////////////////////////////////////////////////////////////
1136 //
1137 // Paragraph
1138 //
1139 /////////////////////////////////////////////////////////////////////
1140
1141 namespace lyx {
1142
1143 Paragraph::Paragraph()
1144         : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1145 {
1146         itemdepth = 0;
1147         params().clear();
1148 }
1149
1150
1151 Paragraph::Paragraph(Paragraph const & par)
1152         : itemdepth(par.itemdepth), insetlist(par.insetlist),
1153         layout_(par.layout_),
1154         text_(par.text_), begin_of_body_(par.begin_of_body_),
1155         pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1156 {
1157         //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1158         InsetList::iterator it = insetlist.begin();
1159         InsetList::iterator end = insetlist.end();
1160         for (; it != end; ++it)
1161                 it->inset = it->inset->clone();
1162 }
1163
1164
1165 Paragraph & Paragraph::operator=(Paragraph const & par)
1166 {
1167         // needed as we will destroy the pimpl_ before copying it
1168         if (&par != this) {
1169                 itemdepth = par.itemdepth;
1170
1171                 insetlist = par.insetlist;
1172                 InsetList::iterator it = insetlist.begin();
1173                 InsetList::iterator end = insetlist.end();
1174                 for (; it != end; ++it)
1175                         it->inset = it->inset->clone();
1176
1177                 layout_ = par.layout();
1178                 text_ = par.text_;
1179                 begin_of_body_ = par.begin_of_body_;
1180
1181                 delete pimpl_;
1182                 pimpl_ = new Pimpl(*par.pimpl_, this);
1183         }
1184         return *this;
1185 }
1186
1187
1188 Paragraph::~Paragraph()
1189 {
1190         delete pimpl_;
1191         //
1192         //lyxerr << "Paragraph::paragraph_id = "
1193         //       << Paragraph::paragraph_id << endl;
1194 }
1195
1196
1197 void Paragraph::write(Buffer const & buf, ostream & os,
1198                           BufferParams const & bparams,
1199                           depth_type & dth) const
1200 {
1201         // The beginning or end of a deeper (i.e. nested) area?
1202         if (dth != params().depth()) {
1203                 if (params().depth() > dth) {
1204                         while (params().depth() > dth) {
1205                                 os << "\n\\begin_deeper";
1206                                 ++dth;
1207                         }
1208                 } else {
1209                         while (params().depth() < dth) {
1210                                 os << "\n\\end_deeper";
1211                                 --dth;
1212                         }
1213                 }
1214         }
1215
1216         // First write the layout
1217         os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1218
1219         params().write(os);
1220
1221         Font font1(Font::ALL_INHERIT, bparams.language);
1222
1223         Change running_change = Change(Change::UNCHANGED);
1224
1225         int column = 0;
1226         for (pos_type i = 0; i <= size(); ++i) {
1227
1228                 Change change = pimpl_->lookupChange(i);
1229                 Changes::lyxMarkChange(os, column, running_change, change);
1230                 running_change = change;
1231
1232                 if (i == size())
1233                         break;
1234
1235                 // Write font changes
1236                 Font font2 = getFontSettings(bparams, i);
1237                 if (font2 != font1) {
1238                         font2.lyxWriteChanges(font1, os);
1239                         column = 0;
1240                         font1 = font2;
1241                 }
1242
1243                 value_type const c = getChar(i);
1244                 switch (c) {
1245                 case META_INSET:
1246                 {
1247                         Inset const * inset = getInset(i);
1248                         if (inset)
1249                                 if (inset->directWrite()) {
1250                                         // international char, let it write
1251                                         // code directly so it's shorter in
1252                                         // the file
1253                                         inset->write(buf, os);
1254                                 } else {
1255                                         if (i)
1256                                                 os << '\n';
1257                                         os << "\\begin_inset ";
1258                                         inset->write(buf, os);
1259                                         os << "\n\\end_inset\n\n";
1260                                         column = 0;
1261                                 }
1262                 }
1263                 break;
1264                 case '\\':
1265                         os << "\n\\backslash\n";
1266                         column = 0;
1267                         break;
1268                 case '.':
1269                         if (i + 1 < size() && getChar(i + 1) == ' ') {
1270                                 os << ".\n";
1271                                 column = 0;
1272                         } else
1273                                 os << '.';
1274                         break;
1275                 default:
1276                         if ((column > 70 && c == ' ')
1277                             || column > 79) {
1278                                 os << '\n';
1279                                 column = 0;
1280                         }
1281                         // this check is to amend a bug. LyX sometimes
1282                         // inserts '\0' this could cause problems.
1283                         if (c != '\0') {
1284                                 std::vector<char> tmp = ucs4_to_utf8(c);
1285                                 tmp.push_back('\0');
1286                                 os << &tmp[0];
1287                         } else
1288                                 lyxerr << "ERROR (Paragraph::writeFile):"
1289                                         " NULL char in structure." << endl;
1290                         ++column;
1291                         break;
1292                 }
1293         }
1294
1295         os << "\n\\end_layout\n";
1296 }
1297
1298
1299 void Paragraph::validate(LaTeXFeatures & features) const
1300 {
1301         pimpl_->validate(features, *layout());
1302 }
1303
1304
1305 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1306 {
1307         return pimpl_->eraseChar(pos, trackChanges);
1308 }
1309
1310
1311 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1312 {
1313         return pimpl_->eraseChars(start, end, trackChanges);
1314 }
1315
1316
1317 void Paragraph::insert(pos_type start, docstring const & str,
1318                        Font const & font, Change const & change)
1319 {
1320         for (size_t i = 0, n = str.size(); i != n ; ++i)
1321                 insertChar(start + i, str[i], font, change);
1322 }
1323
1324
1325 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1326                            bool trackChanges)
1327 {
1328         pimpl_->insertChar(pos, c, Change(trackChanges ?
1329                            Change::INSERTED : Change::UNCHANGED));
1330 }
1331
1332
1333 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1334                            Font const & font, bool trackChanges)
1335 {
1336         pimpl_->insertChar(pos, c, Change(trackChanges ?
1337                            Change::INSERTED : Change::UNCHANGED));
1338         setFont(pos, font);
1339 }
1340
1341
1342 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1343                            Font const & font, Change const & change)
1344 {
1345         pimpl_->insertChar(pos, c, change);
1346         setFont(pos, font);
1347 }
1348
1349
1350 void Paragraph::insertInset(pos_type pos, Inset * inset,
1351                             Change const & change)
1352 {
1353         pimpl_->insertInset(pos, inset, change);
1354 }
1355
1356
1357 void Paragraph::insertInset(pos_type pos, Inset * inset,
1358                             Font const & font, Change const & change)
1359 {
1360         pimpl_->insertInset(pos, inset, change);
1361         // Set the font/language of the inset...
1362         setFont(pos, font);
1363 }
1364
1365
1366 bool Paragraph::insetAllowed(InsetCode code)
1367 {
1368         return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1369 }
1370
1371
1372 // Gets uninstantiated font setting at position.
1373 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1374                                          pos_type pos) const
1375 {
1376         if (pos > size()) {
1377                 lyxerr << " pos: " << pos << " size: " << size() << endl;
1378                 BOOST_ASSERT(pos <= size());
1379         }
1380
1381         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1382         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1383         for (; cit != end; ++cit)
1384                 if (cit->pos() >= pos)
1385                         break;
1386
1387         if (cit != end)
1388                 return cit->font();
1389
1390         if (pos == size() && !empty())
1391                 return getFontSettings(bparams, pos - 1);
1392
1393         return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1394 }
1395
1396
1397 FontSpan Paragraph::fontSpan(pos_type pos) const
1398 {
1399         BOOST_ASSERT(pos <= size());
1400         pos_type start = 0;
1401
1402         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1403         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1404         for (; cit != end; ++cit) {
1405                 if (cit->pos() >= pos) {
1406                         if (pos >= beginOfBody())
1407                                 return FontSpan(std::max(start, beginOfBody()),
1408                                                 cit->pos());
1409                         else
1410                                 return FontSpan(start,
1411                                                 std::min(beginOfBody() - 1,
1412                                                          cit->pos()));
1413                 }
1414                 start = cit->pos() + 1;
1415         }
1416
1417         // This should not happen, but if so, we take no chances.
1418         //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1419         //      << endl;
1420         return FontSpan(pos, pos);
1421 }
1422
1423
1424 // Gets uninstantiated font setting at position 0
1425 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1426 {
1427         if (!empty() && !pimpl_->fontlist.empty())
1428                 return pimpl_->fontlist[0].font();
1429
1430         return Font(Font::ALL_INHERIT, bparams.language);
1431 }
1432
1433
1434 // Gets the fully instantiated font at a given position in a paragraph
1435 // This is basically the same function as Text::GetFont() in text2.cpp.
1436 // The difference is that this one is used for generating the LaTeX file,
1437 // and thus cosmetic "improvements" are disallowed: This has to deliver
1438 // the true picture of the buffer. (Asger)
1439 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1440                                  Font const & outerfont) const
1441 {
1442         BOOST_ASSERT(pos >= 0);
1443
1444         Font font = getFontSettings(bparams, pos);
1445
1446         pos_type const body_pos = beginOfBody();
1447         if (pos < body_pos)
1448                 font.realize(layout_->labelfont);
1449         else
1450                 font.realize(layout_->font);
1451
1452         font.realize(outerfont);
1453         font.realize(bparams.getFont());
1454
1455         return font;
1456 }
1457
1458
1459 Font const Paragraph::getLabelFont
1460         (BufferParams const & bparams, Font const & outerfont) const
1461 {
1462         Font tmpfont = layout()->labelfont;
1463         tmpfont.setLanguage(getParLanguage(bparams));
1464         tmpfont.realize(outerfont);
1465         tmpfont.realize(bparams.getFont());
1466         return tmpfont;
1467 }
1468
1469
1470 Font const Paragraph::getLayoutFont
1471         (BufferParams const & bparams, Font const & outerfont) const
1472 {
1473         Font tmpfont = layout()->font;
1474         tmpfont.setLanguage(getParLanguage(bparams));
1475         tmpfont.realize(outerfont);
1476         tmpfont.realize(bparams.getFont());
1477         return tmpfont;
1478 }
1479
1480
1481 /// Returns the height of the highest font in range
1482 Font_size Paragraph::highestFontInRange
1483         (pos_type startpos, pos_type endpos, Font_size def_size) const
1484 {
1485         if (pimpl_->fontlist.empty())
1486                 return def_size;
1487
1488         Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1489         Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1490         for (; end_it != end; ++end_it) {
1491                 if (end_it->pos() >= endpos)
1492                         break;
1493         }
1494
1495         if (end_it != end)
1496                 ++end_it;
1497
1498         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1499         for (; cit != end; ++cit) {
1500                 if (cit->pos() >= startpos)
1501                         break;
1502         }
1503
1504         Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1505         for (; cit != end_it; ++cit) {
1506                 Font::FONT_SIZE size = cit->font().size();
1507                 if (size == Font::INHERIT_SIZE)
1508                         size = def_size;
1509                 if (size > maxsize && size <= Font::SIZE_HUGER)
1510                         maxsize = size;
1511         }
1512         return maxsize;
1513 }
1514
1515
1516 Paragraph::value_type
1517 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1518 {
1519         value_type c = getChar(pos);
1520         if (!lyxrc.rtl_support)
1521                 return c;
1522
1523         value_type uc = c;
1524         switch (c) {
1525         case '(':
1526                 uc = ')';
1527                 break;
1528         case ')':
1529                 uc = '(';
1530                 break;
1531         case '[':
1532                 uc = ']';
1533                 break;
1534         case ']':
1535                 uc = '[';
1536                 break;
1537         case '{':
1538                 uc = '}';
1539                 break;
1540         case '}':
1541                 uc = '{';
1542                 break;
1543         case '<':
1544                 uc = '>';
1545                 break;
1546         case '>':
1547                 uc = '<';
1548                 break;
1549         }
1550         if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1551                 return uc;
1552         else
1553                 return c;
1554 }
1555
1556
1557 void Paragraph::setFont(pos_type pos, Font const & font)
1558 {
1559         BOOST_ASSERT(pos <= size());
1560
1561         // First, reduce font against layout/label font
1562         // Update: The setCharFont() routine in text2.cpp already
1563         // reduces font, so we don't need to do that here. (Asger)
1564         // No need to simplify this because it will disappear
1565         // in a new kernel. (Asger)
1566         // Next search font table
1567
1568         Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1569         Pimpl::FontList::iterator it = beg;
1570         Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1571         for (; it != endit; ++it) {
1572                 if (it->pos() >= pos)
1573                         break;
1574         }
1575         size_t const i = distance(beg, it);
1576         bool notfound = (it == endit);
1577
1578         if (!notfound && pimpl_->fontlist[i].font() == font)
1579                 return;
1580
1581         bool begin = pos == 0 || notfound ||
1582                 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1583         // Is position pos is a beginning of a font block?
1584         bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1585         // Is position pos is the end of a font block?
1586         if (begin && end) { // A single char block
1587                 if (i + 1 < pimpl_->fontlist.size() &&
1588                     pimpl_->fontlist[i + 1].font() == font) {
1589                         // Merge the singleton block with the next block
1590                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1591                         if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1592                                 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1593                 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1594                         // Merge the singleton block with the previous block
1595                         pimpl_->fontlist[i - 1].pos(pos);
1596                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1597                 } else
1598                         pimpl_->fontlist[i].font(font);
1599         } else if (begin) {
1600                 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1601                         pimpl_->fontlist[i - 1].pos(pos);
1602                 else
1603                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1604                                         Pimpl::FontTable(pos, font));
1605         } else if (end) {
1606                 pimpl_->fontlist[i].pos(pos - 1);
1607                 if (!(i + 1 < pimpl_->fontlist.size() &&
1608                       pimpl_->fontlist[i + 1].font() == font))
1609                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1610                                         Pimpl::FontTable(pos, font));
1611         } else { // The general case. The block is splitted into 3 blocks
1612                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1613                                 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1614                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1615                                 Pimpl::FontTable(pos, font));
1616         }
1617 }
1618
1619
1620 void Paragraph::makeSameLayout(Paragraph const & par)
1621 {
1622         layout(par.layout());
1623         // move to pimpl?
1624         params() = par.params();
1625 }
1626
1627
1628 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1629 {
1630         if (isFreeSpacing())
1631                 return false;
1632
1633         int pos = 0;
1634         int count = 0;
1635
1636         while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1637                 if (eraseChar(pos, trackChanges))
1638                         ++count;
1639                 else
1640                         ++pos;
1641         }
1642
1643         return count > 0 || pos > 0;
1644 }
1645
1646
1647 bool Paragraph::hasSameLayout(Paragraph const & par) const
1648 {
1649         return par.layout() == layout() && params().sameLayout(par.params());
1650 }
1651
1652
1653 depth_type Paragraph::getDepth() const
1654 {
1655         return params().depth();
1656 }
1657
1658
1659 depth_type Paragraph::getMaxDepthAfter() const
1660 {
1661         if (layout()->isEnvironment())
1662                 return params().depth() + 1;
1663         else
1664                 return params().depth();
1665 }
1666
1667
1668 char Paragraph::getAlign() const
1669 {
1670         if (params().align() == LYX_ALIGN_LAYOUT)
1671                 return layout()->align;
1672         else
1673                 return params().align();
1674 }
1675
1676
1677 docstring const & Paragraph::getLabelstring() const
1678 {
1679         return params().labelString();
1680 }
1681
1682
1683 // the next two functions are for the manual labels
1684 docstring const Paragraph::getLabelWidthString() const
1685 {
1686         if (layout()->margintype == MARGIN_MANUAL)
1687                 return params().labelWidthString();
1688         else
1689                 return _("Senseless with this layout!");
1690 }
1691
1692
1693 void Paragraph::setLabelWidthString(docstring const & s)
1694 {
1695         params().labelWidthString(s);
1696 }
1697
1698
1699 docstring const Paragraph::translateIfPossible(docstring const & s,
1700                 BufferParams const & bparams) const
1701 {
1702         if (!support::isAscii(s) || s.empty()) {
1703                 // This must be a user defined layout. We cannot translate
1704                 // this, since gettext accepts only ascii keys.
1705                 return s;
1706         }
1707         // Probably standard layout, try to translate
1708         Messages & m = getMessages(getParLanguage(bparams)->code());
1709         return m.get(to_ascii(s));
1710 }
1711
1712
1713 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1714                 BufferParams const & bparams, bool process_appendix) const
1715 {
1716         TextClass const & tclass = bparams.getTextClass();
1717
1718         docstring fmt;
1719         if (process_appendix && params().appendix())
1720                 fmt = translateIfPossible(layout->labelstring_appendix(),
1721                         bparams);
1722         else
1723                 fmt = translateIfPossible(layout->labelstring(), bparams);
1724
1725         if (fmt.empty() && layout->labeltype == LABEL_COUNTER 
1726             && !layout->counter.empty())
1727                 fmt = "\\the" + layout->counter;
1728
1729         // handle 'inherited level parts' in 'fmt',
1730         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
1731         size_t const i = fmt.find('@', 0);
1732         if (i != docstring::npos) {
1733                 size_t const j = fmt.find('@', i + 1);
1734                 if (j != docstring::npos) {
1735                         docstring parent(fmt, i + 1, j - i - 1);
1736                         docstring label = from_ascii("??");
1737                         if (tclass.hasLayout(parent))
1738                                 docstring label = expandLabel(tclass[parent], bparams,
1739                                                       process_appendix);
1740                         fmt = docstring(fmt, 0, i) + label 
1741                                 + docstring(fmt, j + 1, docstring::npos);
1742                 }
1743         }
1744
1745         return tclass.counters().counterLabel(fmt);
1746 }
1747
1748
1749 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1750 {
1751         layout(new_layout);
1752         LyXAlignment const oldAlign = params().align();
1753         
1754         if (!(oldAlign & layout()->alignpossible)) {
1755                 frontend::Alert::warning(_("Alignment not permitted"), 
1756                         _("The new layout does not permit the alignment previously used.\nSetting to default."));
1757                 params().align(LYX_ALIGN_LAYOUT);
1758         }
1759 }
1760
1761
1762 pos_type Paragraph::beginOfBody() const
1763 {
1764         return begin_of_body_;
1765 }
1766
1767
1768 void Paragraph::setBeginOfBody()
1769 {
1770         if (layout()->labeltype != LABEL_MANUAL) {
1771                 begin_of_body_ = 0;
1772                 return;
1773         }
1774
1775         // Unroll the first two cycles of the loop
1776         // and remember the previous character to
1777         // remove unnecessary getChar() calls
1778         pos_type i = 0;
1779         pos_type end = size();
1780         if (i < end && !isNewline(i)) {
1781                 ++i;
1782                 char_type previous_char = 0;
1783                 char_type temp = 0;
1784                 if (i < end) {
1785                         previous_char = text_[i];
1786                         if (!isNewline(i)) {
1787                                 ++i;
1788                                 while (i < end && previous_char != ' ') {
1789                                         temp = text_[i];
1790                                         if (isNewline(i))
1791                                                 break;
1792                                         ++i;
1793                                         previous_char = temp;
1794                                 }
1795                         }
1796                 }
1797         }
1798
1799         begin_of_body_ = i;
1800 }
1801
1802
1803 // returns -1 if inset not found
1804 int Paragraph::getPositionOfInset(Inset const * inset) const
1805 {
1806         // Find the entry.
1807         InsetList::const_iterator it = insetlist.begin();
1808         InsetList::const_iterator end = insetlist.end();
1809         for (; it != end; ++it)
1810                 if (it->inset == inset)
1811                         return it->pos;
1812         return -1;
1813 }
1814
1815
1816 InsetBibitem * Paragraph::bibitem() const
1817 {
1818         if (!insetlist.empty()) {
1819                 Inset * inset = insetlist.begin()->inset;
1820                 if (inset->lyxCode() == BIBITEM_CODE)
1821                         return static_cast<InsetBibitem *>(inset);
1822         }
1823         return 0;
1824 }
1825
1826
1827 bool Paragraph::forceDefaultParagraphs() const
1828 {
1829         return inInset() && inInset()->forceDefaultParagraphs(0);
1830 }
1831
1832
1833 namespace {
1834
1835 // paragraphs inside floats need different alignment tags to avoid
1836 // unwanted space
1837
1838 bool noTrivlistCentering(InsetCode code)
1839 {
1840         return code == FLOAT_CODE || code == WRAP_CODE;
1841 }
1842
1843
1844 string correction(string const & orig)
1845 {
1846         if (orig == "flushleft")
1847                 return "raggedright";
1848         if (orig == "flushright")
1849                 return "raggedleft";
1850         if (orig == "center")
1851                 return "centering";
1852         return orig;
1853 }
1854
1855
1856 string const corrected_env(string const & suffix, string const & env,
1857         InsetCode code)
1858 {
1859         string output = suffix + "{";
1860         if (noTrivlistCentering(code))
1861                 output += correction(env);
1862         else
1863                 output += env;
1864         output += "}";
1865         if (suffix == "\\begin")
1866                 output += "\n";
1867         return output;
1868 }
1869
1870
1871 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1872 {
1873         if (!contains(str, "\n"))
1874                 column += str.size();
1875         else {
1876                 string tmp;
1877                 texrow.newline();
1878                 column = rsplit(str, tmp, '\n').size();
1879         }
1880 }
1881
1882 } // namespace anon
1883
1884
1885 // This could go to ParagraphParameters if we want to
1886 int Paragraph::startTeXParParams(BufferParams const & bparams,
1887                                  odocstream & os, TexRow & texrow,
1888                                  bool moving_arg) const
1889 {
1890         int column = 0;
1891
1892         if (params().noindent()) {
1893                 os << "\\noindent ";
1894                 column += 10;
1895         }
1896         
1897         LyXAlignment const curAlign = params().align();
1898
1899         if (curAlign == layout()->align)
1900                 return column;
1901
1902         switch (curAlign) {
1903         case LYX_ALIGN_NONE:
1904         case LYX_ALIGN_BLOCK:
1905         case LYX_ALIGN_LAYOUT:
1906         case LYX_ALIGN_SPECIAL:
1907                 break;
1908         case LYX_ALIGN_LEFT:
1909         case LYX_ALIGN_RIGHT:
1910         case LYX_ALIGN_CENTER:
1911                 if (moving_arg) {
1912                         os << "\\protect";
1913                         column += 8;
1914                 }
1915                 break;
1916         }
1917
1918         switch (curAlign) {
1919         case LYX_ALIGN_NONE:
1920         case LYX_ALIGN_BLOCK:
1921         case LYX_ALIGN_LAYOUT:
1922         case LYX_ALIGN_SPECIAL:
1923                 break;
1924         case LYX_ALIGN_LEFT: {
1925                 string output;
1926                 if (getParLanguage(bparams)->babel() != "hebrew")
1927                         output = corrected_env("\\begin", "flushleft", ownerCode());
1928                 else
1929                         output = corrected_env("\\begin", "flushright", ownerCode());
1930                 os << from_ascii(output);
1931                 adjust_row_column(output, texrow, column);
1932                 break;
1933         } case LYX_ALIGN_RIGHT: {
1934                 string output;
1935                 if (getParLanguage(bparams)->babel() != "hebrew")
1936                         output = corrected_env("\\begin", "flushright", ownerCode());
1937                 else
1938                         output = corrected_env("\\begin", "flushleft", ownerCode());
1939                 os << from_ascii(output);
1940                 adjust_row_column(output, texrow, column);
1941                 break;
1942         } case LYX_ALIGN_CENTER: {
1943                 string output;
1944                 output = corrected_env("\\begin", "center", ownerCode());
1945                 os << from_ascii(output);
1946                 adjust_row_column(output, texrow, column);
1947                 break;
1948         }
1949         }
1950
1951         return column;
1952 }
1953
1954
1955 // This could go to ParagraphParameters if we want to
1956 int Paragraph::endTeXParParams(BufferParams const & bparams,
1957                                odocstream & os, TexRow & texrow,
1958                                bool moving_arg) const
1959 {
1960         int column = 0;
1961
1962         switch (params().align()) {
1963         case LYX_ALIGN_NONE:
1964         case LYX_ALIGN_BLOCK:
1965         case LYX_ALIGN_LAYOUT:
1966         case LYX_ALIGN_SPECIAL:
1967                 break;
1968         case LYX_ALIGN_LEFT:
1969         case LYX_ALIGN_RIGHT:
1970         case LYX_ALIGN_CENTER:
1971                 if (moving_arg) {
1972                         os << "\\protect";
1973                         column = 8;
1974                 }
1975                 break;
1976         }
1977
1978         switch (params().align()) {
1979         case LYX_ALIGN_NONE:
1980         case LYX_ALIGN_BLOCK:
1981         case LYX_ALIGN_LAYOUT:
1982         case LYX_ALIGN_SPECIAL:
1983                 break;
1984         case LYX_ALIGN_LEFT: {
1985                 string output;
1986                 if (getParLanguage(bparams)->babel() != "hebrew")
1987                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1988                 else
1989                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1990                 os << from_ascii(output);
1991                 adjust_row_column(output, texrow, column);
1992                 break;
1993         } case LYX_ALIGN_RIGHT: {
1994                 string output;
1995                 if (getParLanguage(bparams)->babel() != "hebrew")
1996                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1997                 else
1998                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1999                 os << from_ascii(output);
2000                 adjust_row_column(output, texrow, column);
2001                 break;
2002         } case LYX_ALIGN_CENTER: {
2003                 string output;
2004                 output = corrected_env("\n\\par\\end", "center", ownerCode());
2005                 os << from_ascii(output);
2006                 adjust_row_column(output, texrow, column);
2007                 break;
2008         }
2009         }
2010
2011         return column;
2012 }
2013
2014
2015 // This one spits out the text of the paragraph
2016 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
2017                                 BufferParams const & bparams,
2018                                 Font const & outerfont,
2019                                 odocstream & os, TexRow & texrow,
2020                                 OutputParams const & runparams) const
2021 {
2022         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...     " << this << endl;
2023
2024         bool return_value = false;
2025
2026         LayoutPtr style;
2027
2028         // well we have to check if we are in an inset with unlimited
2029         // length (all in one row) if that is true then we don't allow
2030         // any special options in the paragraph and also we don't allow
2031         // any environment other than the default layout of the text class
2032         // to be valid!
2033         bool asdefault = forceDefaultParagraphs();
2034
2035         if (asdefault) {
2036                 style = bparams.getTextClass().defaultLayout();
2037         } else {
2038                 style = layout();
2039         }
2040
2041         // Current base font for all inherited font changes, without any
2042         // change caused by an individual character, except for the language:
2043         // It is set to the language of the first character.
2044         // As long as we are in the label, this font is the base font of the
2045         // label. Before the first body character it is set to the base font
2046         // of the body.
2047         Font basefont;
2048
2049         // Maybe we have to create a optional argument.
2050         pos_type body_pos = beginOfBody();
2051         unsigned int column = 0;
2052
2053         if (body_pos > 0) {
2054                 // the optional argument is kept in curly brackets in
2055                 // case it contains a ']'
2056                 os << "[{";
2057                 column += 2;
2058                 basefont = getLabelFont(bparams, outerfont);
2059         } else {
2060                 basefont = getLayoutFont(bparams, outerfont);
2061         }
2062
2063         // Which font is currently active?
2064         Font running_font(basefont);
2065         // Do we have an open font change?
2066         bool open_font = false;
2067
2068         Change runningChange = Change(Change::UNCHANGED);
2069
2070         texrow.start(id(), 0);
2071
2072         // if the paragraph is empty, the loop will not be entered at all
2073         if (empty()) {
2074                 if (style->isCommand()) {
2075                         os << '{';
2076                         ++column;
2077                 }
2078                 if (!asdefault)
2079                         column += startTeXParParams(bparams, os, texrow,
2080                                                     runparams.moving_arg);
2081         }
2082
2083         for (pos_type i = 0; i < size(); ++i) {
2084                 // First char in paragraph or after label?
2085                 if (i == body_pos) {
2086                         if (body_pos > 0) {
2087                                 if (open_font) {
2088                                         column += running_font.latexWriteEndChanges(
2089                                                 os, bparams, runparams,
2090                                                 basefont, basefont);
2091                                         open_font = false;
2092                                 }
2093                                 basefont = getLayoutFont(bparams, outerfont);
2094                                 running_font = basefont;
2095
2096                                 column += Changes::latexMarkChange(os, bparams,
2097                                                 runningChange, Change(Change::UNCHANGED));
2098                                 runningChange = Change(Change::UNCHANGED);
2099
2100                                 os << "}] ";
2101                                 column +=3;
2102                         }
2103                         if (style->isCommand()) {
2104                                 os << '{';
2105                                 ++column;
2106                         }
2107
2108                         if (!asdefault)
2109                                 column += startTeXParParams(bparams, os,
2110                                                             texrow,
2111                                                             runparams.moving_arg);
2112                 }
2113
2114                 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2115                                                                  : pimpl_->lookupChange(i);
2116
2117                 if (bparams.outputChanges && runningChange != change) {
2118                         if (open_font) {
2119                                 column += running_font.latexWriteEndChanges(
2120                                                 os, bparams, runparams, basefont, basefont);
2121                                 open_font = false;
2122                         }
2123                         basefont = getLayoutFont(bparams, outerfont);
2124                         running_font = basefont;
2125
2126                         column += Changes::latexMarkChange(os, bparams, runningChange, change);
2127                         runningChange = change;
2128                 }
2129
2130                 // do not output text which is marked deleted
2131                 // if change tracking output is disabled
2132                 if (!bparams.outputChanges && change.type == Change::DELETED) {
2133                         continue;
2134                 }
2135
2136                 ++column;
2137
2138                 value_type const c = getChar(i);
2139
2140                 // Fully instantiated font
2141                 Font const font = getFont(bparams, i, outerfont);
2142
2143                 Font const last_font = running_font;
2144
2145                 // Do we need to close the previous font?
2146                 if (open_font &&
2147                     (font != running_font ||
2148                      font.language() != running_font.language()))
2149                 {
2150                         column += running_font.latexWriteEndChanges(
2151                                         os, bparams, runparams, basefont,
2152                                         (i == body_pos-1) ? basefont : font);
2153                         running_font = basefont;
2154                         open_font = false;
2155                 }
2156
2157                 // Switch file encoding if necessary
2158                 if (runparams.encoding->package() == Encoding::inputenc &&
2159                     font.language()->encoding()->package() == Encoding::inputenc) {
2160                         std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2161                                         runparams.moving_arg, *(runparams.encoding),
2162                                         *(font.language()->encoding()));
2163                         if (enc_switch.first) {
2164                                 column += enc_switch.second;
2165                                 runparams.encoding = font.language()->encoding();
2166                         }
2167                 }
2168
2169                 // Do we need to change font?
2170                 if ((font != running_font ||
2171                      font.language() != running_font.language()) &&
2172                         i != body_pos - 1)
2173                 {
2174                         odocstringstream ods;
2175                         column += font.latexWriteStartChanges(ods, bparams,
2176                                                               runparams, basefont,
2177                                                               last_font);
2178                         running_font = font;
2179                         open_font = true;
2180                         docstring fontchange = ods.str();
2181                         // check if the fontchange ends with a trailing blank
2182                         // (like "\small " (see bug 3382)
2183                         if (suffixIs(fontchange, ' ') && c == ' ')
2184                                 os << fontchange.substr(0, fontchange.size() - 1) 
2185                                    << from_ascii("{}");
2186                         else
2187                                 os << fontchange;
2188                 }
2189
2190                 if (c == ' ') {
2191                         // Do not print the separation of the optional argument
2192                         // if style->pass_thru is false. This works because
2193                         // simpleTeXSpecialChars ignores spaces if
2194                         // style->pass_thru is false.
2195                         if (i != body_pos - 1) {
2196                                 if (pimpl_->simpleTeXBlanks(
2197                                                 *(runparams.encoding), os, texrow,
2198                                                 i, column, font, *style))
2199                                         // A surrogate pair was output. We
2200                                         // must not call simpleTeXSpecialChars
2201                                         // in this iteration, since
2202                                         // simpleTeXBlanks incremented i, and
2203                                         // simpleTeXSpecialChars would output
2204                                         // the combining character again.
2205                                         continue;
2206                         }
2207                 }
2208
2209                 OutputParams rp = runparams;
2210                 rp.free_spacing = style->free_spacing;
2211                 rp.local_font = &font;
2212                 rp.intitle = style->intitle;
2213                 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2214                                         texrow, rp, running_font,
2215                                         basefont, outerfont, open_font,
2216                                         runningChange, *style, i, column, c);
2217
2218                 // Set the encoding to that returned from simpleTeXSpecialChars (see
2219                 // comment for encoding member in OutputParams.h)
2220                 runparams.encoding = rp.encoding;
2221         }
2222
2223         // If we have an open font definition, we have to close it
2224         if (open_font) {
2225 #ifdef FIXED_LANGUAGE_END_DETECTION
2226                 if (next_) {
2227                         running_font
2228                                 .latexWriteEndChanges(os, bparams, runparams,
2229                                         basefont,
2230                                         next_->getFont(bparams, 0, outerfont));
2231                 } else {
2232                         running_font.latexWriteEndChanges(os, bparams,
2233                                         runparams, basefont, basefont);
2234                 }
2235 #else
2236 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2237 //FIXME: there as we start another \selectlanguage with the next paragraph if
2238 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2239                 running_font.latexWriteEndChanges(os, bparams, runparams,
2240                                 basefont, basefont);
2241 #endif
2242         }
2243
2244         column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2245
2246         // Needed if there is an optional argument but no contents.
2247         if (body_pos > 0 && body_pos == size()) {
2248                 os << "}]~";
2249                 return_value = false;
2250         }
2251
2252         if (!asdefault) {
2253                 column += endTeXParParams(bparams, os, texrow,
2254                                           runparams.moving_arg);
2255         }
2256
2257         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2258         return return_value;
2259 }
2260
2261
2262 namespace {
2263
2264 enum PAR_TAG {
2265         PAR_NONE=0,
2266         TT = 1,
2267         SF = 2,
2268         BF = 4,
2269         IT = 8,
2270         SL = 16,
2271         EM = 32
2272 };
2273
2274
2275 string tag_name(PAR_TAG const & pt) {
2276         switch (pt) {
2277         case PAR_NONE: return "!-- --";
2278         case TT: return "tt";
2279         case SF: return "sf";
2280         case BF: return "bf";
2281         case IT: return "it";
2282         case SL: return "sl";
2283         case EM: return "em";
2284         }
2285         return "";
2286 }
2287
2288
2289 inline
2290 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2291 {
2292         p1 = static_cast<PAR_TAG>(p1 | p2);
2293 }
2294
2295
2296 inline
2297 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2298 {
2299         p1 = static_cast<PAR_TAG>(p1 & ~p2);
2300 }
2301
2302 } // anon
2303
2304
2305 bool Paragraph::emptyTag() const
2306 {
2307         for (pos_type i = 0; i < size(); ++i) {
2308                 if (isInset(i)) {
2309                         Inset const * inset = getInset(i);
2310                         InsetCode lyx_code = inset->lyxCode();
2311                         if (lyx_code != TOC_CODE &&
2312                             lyx_code != INCLUDE_CODE &&
2313                             lyx_code != GRAPHICS_CODE &&
2314                             lyx_code != ERT_CODE &&
2315                             lyx_code != LISTINGS_CODE &&
2316                             lyx_code != FLOAT_CODE &&
2317                             lyx_code != TABULAR_CODE) {
2318                                 return false;
2319                         }
2320                 } else {
2321                         value_type c = getChar(i);
2322                         if (c != ' ' && c != '\t')
2323                                 return false;
2324                 }
2325         }
2326         return true;
2327 }
2328
2329
2330 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2331 {
2332         for (pos_type i = 0; i < size(); ++i) {
2333                 if (isInset(i)) {
2334                         Inset const * inset = getInset(i);
2335                         InsetCode lyx_code = inset->lyxCode();
2336                         if (lyx_code == LABEL_CODE) {
2337                                 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2338                                 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2339                         }
2340                 }
2341
2342         }
2343         return string();
2344 }
2345
2346
2347 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2348 {
2349         pos_type i;
2350         for (i = 0; i < size(); ++i) {
2351                 if (isInset(i)) {
2352                         Inset const * inset = getInset(i);
2353                         inset->docbook(buf, os, runparams);
2354                 } else {
2355                         value_type c = getChar(i);
2356                         if (c == ' ')
2357                                 break;
2358                         os << sgml::escapeChar(c);
2359                 }
2360         }
2361         return i;
2362 }
2363
2364
2365 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2366 {
2367         Font font_old;
2368
2369         for (pos_type i = initial; i < size(); ++i) {
2370                 Font font = getFont(buf.params(), i, outerfont);
2371                 if (isInset(i))
2372                         return false;
2373                 if (i != initial && font != font_old)
2374                         return false;
2375                 font_old = font;
2376         }
2377
2378         return true;
2379 }
2380
2381
2382 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2383                                     odocstream & os,
2384                                     OutputParams const & runparams,
2385                                     Font const & outerfont,
2386                                     pos_type initial) const
2387 {
2388         bool emph_flag = false;
2389
2390         LayoutPtr const & style = layout();
2391         Font font_old =
2392                 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2393
2394         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2395                 os << "]]>";
2396
2397         // parsing main loop
2398         for (pos_type i = initial; i < size(); ++i) {
2399                 Font font = getFont(buf.params(), i, outerfont);
2400
2401                 // handle <emphasis> tag
2402                 if (font_old.emph() != font.emph()) {
2403                         if (font.emph() == Font::ON) {
2404                                 os << "<emphasis>";
2405                                 emph_flag = true;
2406                         } else if (i != initial) {
2407                                 os << "</emphasis>";
2408                                 emph_flag = false;
2409                         }
2410                 }
2411
2412                 if (isInset(i)) {
2413                         Inset const * inset = getInset(i);
2414                         inset->docbook(buf, os, runparams);
2415                 } else {
2416                         value_type c = getChar(i);
2417
2418                         if (style->pass_thru)
2419                                 os.put(c);
2420                         else
2421                                 os << sgml::escapeChar(c);
2422                 }
2423                 font_old = font;
2424         }
2425
2426         if (emph_flag) {
2427                 os << "</emphasis>";
2428         }
2429
2430         if (style->free_spacing)
2431                 os << '\n';
2432         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2433                 os << "<![CDATA[";
2434 }
2435
2436
2437 bool Paragraph::isHfill(pos_type pos) const
2438 {
2439         return isInset(pos)
2440                 && getInset(pos)->lyxCode() == HFILL_CODE;
2441 }
2442
2443
2444 bool Paragraph::isNewline(pos_type pos) const
2445 {
2446         return isInset(pos)
2447                 && getInset(pos)->lyxCode() == NEWLINE_CODE;
2448 }
2449
2450
2451 bool Paragraph::isLineSeparator(pos_type pos) const
2452 {
2453         value_type const c = getChar(pos);
2454         return isLineSeparatorChar(c)
2455                 || (c == Paragraph::META_INSET && getInset(pos) &&
2456                 getInset(pos)->isLineSeparator());
2457 }
2458
2459
2460 /// Used by the spellchecker
2461 bool Paragraph::isLetter(pos_type pos) const
2462 {
2463         if (isInset(pos))
2464                 return getInset(pos)->isLetter();
2465         else {
2466                 value_type const c = getChar(pos);
2467                 return isLetterChar(c) || isDigit(c);
2468         }
2469 }
2470
2471
2472 Language const *
2473 Paragraph::getParLanguage(BufferParams const & bparams) const
2474 {
2475         if (!empty())
2476                 return getFirstFontSettings(bparams).language();
2477         // FIXME: we should check the prev par as well (Lgb)
2478         return bparams.language;
2479 }
2480
2481
2482 bool Paragraph::isRTL(BufferParams const & bparams) const
2483 {
2484         return lyxrc.rtl_support
2485                 && getParLanguage(bparams)->rightToLeft()
2486                 && ownerCode() != ERT_CODE
2487                 && ownerCode() != LISTINGS_CODE;
2488 }
2489
2490
2491 void Paragraph::changeLanguage(BufferParams const & bparams,
2492                                Language const * from, Language const * to)
2493 {
2494         // change language including dummy font change at the end
2495         for (pos_type i = 0; i <= size(); ++i) {
2496                 Font font = getFontSettings(bparams, i);
2497                 if (font.language() == from) {
2498                         font.setLanguage(to);
2499                         setFont(i, font);
2500                 }
2501         }
2502 }
2503
2504
2505 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2506 {
2507         Language const * doc_language = bparams.language;
2508         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2509         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2510
2511         for (; cit != end; ++cit)
2512                 if (cit->font().language() != ignore_language &&
2513                     cit->font().language() != latex_language &&
2514                     cit->font().language() != doc_language)
2515                         return true;
2516         return false;
2517 }
2518
2519
2520 // Convert the paragraph to a string.
2521 // Used for building the table of contents
2522 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2523 {
2524         return asString(buffer, 0, size(), label);
2525 }
2526
2527
2528 docstring const Paragraph::asString(Buffer const & buffer,
2529                                  pos_type beg, pos_type end, bool label) const
2530 {
2531
2532         odocstringstream os;
2533
2534         if (beg == 0 && label && !params().labelString().empty())
2535                 os << params().labelString() << ' ';
2536
2537         for (pos_type i = beg; i < end; ++i) {
2538                 value_type const c = getChar(i);
2539                 if (isPrintable(c))
2540                         os.put(c);
2541                 else if (c == META_INSET)
2542                         getInset(i)->textString(buffer, os);
2543         }
2544
2545         return os.str();
2546 }
2547
2548
2549 void Paragraph::setInsetOwner(Inset * inset)
2550 {
2551         pimpl_->inset_owner = inset;
2552 }
2553
2554
2555 Change const & Paragraph::lookupChange(pos_type pos) const
2556 {
2557         BOOST_ASSERT(pos <= size());
2558         return pimpl_->lookupChange(pos);
2559 }
2560
2561
2562 bool Paragraph::isChanged(pos_type start, pos_type end) const
2563 {
2564         return pimpl_->isChanged(start, end);
2565 }
2566
2567
2568 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2569 {
2570         return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2571 }
2572
2573
2574 void Paragraph::setChange(Change const & change)
2575 {
2576         pimpl_->setChange(change);
2577 }
2578
2579
2580 void Paragraph::setChange(pos_type pos, Change const & change)
2581 {
2582         pimpl_->setChange(pos, change);
2583 }
2584
2585
2586 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2587 {
2588         return pimpl_->acceptChanges(bparams, start, end);
2589 }
2590
2591
2592 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2593 {
2594         return pimpl_->rejectChanges(bparams, start, end);
2595 }
2596
2597
2598 int Paragraph::id() const
2599 {
2600         return pimpl_->id_;
2601 }
2602
2603
2604 LayoutPtr const & Paragraph::layout() const
2605 {
2606         return layout_;
2607 }
2608
2609
2610 void Paragraph::layout(LayoutPtr const & new_layout)
2611 {
2612         layout_ = new_layout;
2613 }
2614
2615
2616 Inset * Paragraph::inInset() const
2617 {
2618         return pimpl_->inset_owner;
2619 }
2620
2621
2622 InsetCode Paragraph::ownerCode() const
2623 {
2624         return pimpl_->inset_owner
2625                 ? pimpl_->inset_owner->lyxCode() : NO_CODE;
2626 }
2627
2628
2629 ParagraphParameters & Paragraph::params()
2630 {
2631         return pimpl_->params;
2632 }
2633
2634
2635 ParagraphParameters const & Paragraph::params() const
2636 {
2637         return pimpl_->params;
2638 }
2639
2640
2641 bool Paragraph::isFreeSpacing() const
2642 {
2643         if (layout()->free_spacing)
2644                 return true;
2645
2646         // for now we just need this, later should we need this in some
2647         // other way we can always add a function to Inset too.
2648         return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2649 }
2650
2651
2652 bool Paragraph::allowEmpty() const
2653 {
2654         if (layout()->keepempty)
2655                 return true;
2656         return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2657 }
2658
2659
2660 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2661 {
2662         if (!Encodings::is_arabic(c))
2663                 return c;
2664
2665         value_type prev_char = ' ';
2666         value_type next_char = ' ';
2667
2668         for (pos_type i = pos - 1; i >= 0; --i) {
2669                 value_type const par_char = getChar(i);
2670                 if (!Encodings::isComposeChar_arabic(par_char)) {
2671                         prev_char = par_char;
2672                         break;
2673                 }
2674         }
2675
2676         for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2677                 value_type const par_char = getChar(i);
2678                 if (!Encodings::isComposeChar_arabic(par_char)) {
2679                         next_char = par_char;
2680                         break;
2681                 }
2682         }
2683
2684         if (Encodings::is_arabic(next_char)) {
2685                 if (Encodings::is_arabic(prev_char) &&
2686                         !Encodings::is_arabic_special(prev_char))
2687                         return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2688                 else
2689                         return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2690         } else {
2691                 if (Encodings::is_arabic(prev_char) &&
2692                         !Encodings::is_arabic_special(prev_char))
2693                         return Encodings::transformChar(c, Encodings::FORM_FINAL);
2694                 else
2695                         return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2696         }
2697 }
2698
2699
2700 int Paragraph::checkBiblio(bool track_changes)
2701 {
2702         //FIXME From JS:
2703         //This is getting more and more a mess. ...We really should clean
2704         //up this bibitem issue for 1.6. See also bug 2743.
2705
2706         // Add bibitem insets if necessary
2707         if (layout()->labeltype != LABEL_BIBLIO)
2708                 return 0;
2709
2710         bool hasbibitem = !insetlist.empty()
2711                 // Insist on it being in pos 0
2712                 && getChar(0) == Paragraph::META_INSET
2713                 && insetlist.begin()->inset->lyxCode() == BIBITEM_CODE;
2714
2715         docstring oldkey;
2716         docstring oldlabel;
2717
2718         // remove a bibitem in pos != 0
2719         // restore it later in pos 0 if necessary
2720         // (e.g. if a user inserts contents _before_ the item)
2721         // we're assuming there's only one of these, which there
2722         // should be.
2723         int erasedInsetPosition = -1;
2724         InsetList::iterator it = insetlist.begin();
2725         InsetList::iterator end = insetlist.end();
2726         for (; it != end; ++it)
2727                 if (it->inset->lyxCode() == BIBITEM_CODE
2728                     && it->pos > 0) {
2729                         InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2730                         oldkey = olditem->getParam("key");
2731                         oldlabel = olditem->getParam("label");
2732                         erasedInsetPosition = it->pos;
2733                         eraseChar(erasedInsetPosition, track_changes);
2734                         break;
2735         }
2736
2737         //There was an InsetBibitem at the beginning, and we didn't
2738         //have to erase one.
2739         if (hasbibitem && erasedInsetPosition < 0)
2740                         return 0;
2741
2742         //There was an InsetBibitem at the beginning and we did have to
2743         //erase one. So we give its properties to the beginning inset.
2744         if (hasbibitem) {
2745                 InsetBibitem * inset =
2746                         static_cast<InsetBibitem *>(insetlist.begin()->inset);
2747                 if (!oldkey.empty())
2748                         inset->setParam("key", oldkey);
2749                 inset->setParam("label", oldlabel);
2750                 return -erasedInsetPosition;
2751         }
2752
2753         //There was no inset at the beginning, so we need to create one with
2754         //the key and label of the one we erased.
2755         InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2756         // restore values of previously deleted item in this par.
2757         if (!oldkey.empty())
2758                 inset->setParam("key", oldkey);
2759         inset->setParam("label", oldlabel);
2760         insertInset(0, static_cast<Inset *>(inset),
2761                     Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2762
2763         return 1;
2764 }
2765
2766
2767 void Paragraph::checkAuthors(AuthorList const & authorList)
2768 {
2769         pimpl_->changes_.checkAuthors(authorList);
2770 }
2771
2772 } // namespace lyx