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