]> git.lyx.org Git - lyx.git/blob - src/Paragraph.cpp
* Paragraph::getFontSettings(): small optimisation: avoid a Font copy
[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         Layout_ptr const & lout = layout();
1382
1383         pos_type const body_pos = beginOfBody();
1384
1385         Font * layoutfont;
1386         if (pos < body_pos)
1387                 layoutfont = &lout->labelfont;
1388         else
1389                 layoutfont = &lout->font;
1390
1391         Font font = getFontSettings(bparams, pos);
1392         font.realize(*layoutfont);
1393         font.realize(outerfont);
1394         font.realize(bparams.getFont());
1395
1396         return font;
1397 }
1398
1399
1400 Font const Paragraph::getLabelFont
1401         (BufferParams const & bparams, Font const & outerfont) const
1402 {
1403         Font tmpfont = layout()->labelfont;
1404         tmpfont.setLanguage(getParLanguage(bparams));
1405         tmpfont.realize(outerfont);
1406         tmpfont.realize(bparams.getFont());
1407         return tmpfont;
1408 }
1409
1410
1411 Font const Paragraph::getLayoutFont
1412         (BufferParams const & bparams, Font const & outerfont) const
1413 {
1414         Font tmpfont = layout()->font;
1415         tmpfont.setLanguage(getParLanguage(bparams));
1416         tmpfont.realize(outerfont);
1417         tmpfont.realize(bparams.getFont());
1418         return tmpfont;
1419 }
1420
1421
1422 /// Returns the height of the highest font in range
1423 Font_size Paragraph::highestFontInRange
1424         (pos_type startpos, pos_type endpos, Font_size def_size) const
1425 {
1426         if (pimpl_->fontlist.empty())
1427                 return def_size;
1428
1429         Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1430         Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1431         for (; end_it != end; ++end_it) {
1432                 if (end_it->pos() >= endpos)
1433                         break;
1434         }
1435
1436         if (end_it != end)
1437                 ++end_it;
1438
1439         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1440         for (; cit != end; ++cit) {
1441                 if (cit->pos() >= startpos)
1442                         break;
1443         }
1444
1445         Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1446         for (; cit != end_it; ++cit) {
1447                 Font::FONT_SIZE size = cit->font().size();
1448                 if (size == Font::INHERIT_SIZE)
1449                         size = def_size;
1450                 if (size > maxsize && size <= Font::SIZE_HUGER)
1451                         maxsize = size;
1452         }
1453         return maxsize;
1454 }
1455
1456
1457 Paragraph::value_type
1458 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1459 {
1460         value_type c = getChar(pos);
1461         if (!lyxrc.rtl_support)
1462                 return c;
1463
1464         value_type uc = c;
1465         switch (c) {
1466         case '(':
1467                 uc = ')';
1468                 break;
1469         case ')':
1470                 uc = '(';
1471                 break;
1472         case '[':
1473                 uc = ']';
1474                 break;
1475         case ']':
1476                 uc = '[';
1477                 break;
1478         case '{':
1479                 uc = '}';
1480                 break;
1481         case '}':
1482                 uc = '{';
1483                 break;
1484         case '<':
1485                 uc = '>';
1486                 break;
1487         case '>':
1488                 uc = '<';
1489                 break;
1490         }
1491         if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1492                 return uc;
1493         else
1494                 return c;
1495 }
1496
1497
1498 void Paragraph::setFont(pos_type pos, Font const & font)
1499 {
1500         BOOST_ASSERT(pos <= size());
1501
1502         // First, reduce font against layout/label font
1503         // Update: The setCharFont() routine in text2.cpp already
1504         // reduces font, so we don't need to do that here. (Asger)
1505         // No need to simplify this because it will disappear
1506         // in a new kernel. (Asger)
1507         // Next search font table
1508
1509         Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1510         Pimpl::FontList::iterator it = beg;
1511         Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1512         for (; it != endit; ++it) {
1513                 if (it->pos() >= pos)
1514                         break;
1515         }
1516         size_t const i = distance(beg, it);
1517         bool notfound = (it == endit);
1518
1519         if (!notfound && pimpl_->fontlist[i].font() == font)
1520                 return;
1521
1522         bool begin = pos == 0 || notfound ||
1523                 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1524         // Is position pos is a beginning of a font block?
1525         bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1526         // Is position pos is the end of a font block?
1527         if (begin && end) { // A single char block
1528                 if (i + 1 < pimpl_->fontlist.size() &&
1529                     pimpl_->fontlist[i + 1].font() == font) {
1530                         // Merge the singleton block with the next block
1531                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1532                         if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1533                                 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1534                 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1535                         // Merge the singleton block with the previous block
1536                         pimpl_->fontlist[i - 1].pos(pos);
1537                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1538                 } else
1539                         pimpl_->fontlist[i].font(font);
1540         } else if (begin) {
1541                 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1542                         pimpl_->fontlist[i - 1].pos(pos);
1543                 else
1544                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1545                                         Pimpl::FontTable(pos, font));
1546         } else if (end) {
1547                 pimpl_->fontlist[i].pos(pos - 1);
1548                 if (!(i + 1 < pimpl_->fontlist.size() &&
1549                       pimpl_->fontlist[i + 1].font() == font))
1550                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1551                                         Pimpl::FontTable(pos, font));
1552         } else { // The general case. The block is splitted into 3 blocks
1553                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1554                                 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1555                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1556                                 Pimpl::FontTable(pos, font));
1557         }
1558 }
1559
1560
1561 void Paragraph::makeSameLayout(Paragraph const & par)
1562 {
1563         layout(par.layout());
1564         // move to pimpl?
1565         params() = par.params();
1566 }
1567
1568
1569 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1570 {
1571         if (isFreeSpacing())
1572                 return false;
1573
1574         int pos = 0;
1575         int count = 0;
1576
1577         while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1578                 if (eraseChar(pos, trackChanges))
1579                         ++count;
1580                 else
1581                         ++pos;
1582         }
1583
1584         return count > 0 || pos > 0;
1585 }
1586
1587
1588 bool Paragraph::hasSameLayout(Paragraph const & par) const
1589 {
1590         return par.layout() == layout() && params().sameLayout(par.params());
1591 }
1592
1593
1594 depth_type Paragraph::getDepth() const
1595 {
1596         return params().depth();
1597 }
1598
1599
1600 depth_type Paragraph::getMaxDepthAfter() const
1601 {
1602         if (layout()->isEnvironment())
1603                 return params().depth() + 1;
1604         else
1605                 return params().depth();
1606 }
1607
1608
1609 char Paragraph::getAlign() const
1610 {
1611         if (params().align() == LYX_ALIGN_LAYOUT)
1612                 return layout()->align;
1613         else
1614                 return params().align();
1615 }
1616
1617
1618 docstring const & Paragraph::getLabelstring() const
1619 {
1620         return params().labelString();
1621 }
1622
1623
1624 // the next two functions are for the manual labels
1625 docstring const Paragraph::getLabelWidthString() const
1626 {
1627         if (!params().labelWidthString().empty())
1628                 return params().labelWidthString();
1629         else
1630                 return _("Senseless with this layout!");
1631 }
1632
1633
1634 void Paragraph::setLabelWidthString(docstring const & s)
1635 {
1636         params().labelWidthString(s);
1637 }
1638
1639
1640 docstring const Paragraph::translateIfPossible(docstring const & s,
1641                 BufferParams const & bparams) const
1642 {
1643         if (!support::isAscii(s) || s.empty()) {
1644                 // This must be a user defined layout. We cannot translate
1645                 // this, since gettext accepts only ascii keys.
1646                 return s;
1647         }
1648         // Probably standard layout, try to translate
1649         Messages & m = getMessages(getParLanguage(bparams)->code());
1650         return m.get(to_ascii(s));
1651 }
1652
1653
1654 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1655                 BufferParams const & bparams, bool process_appendix) const
1656 {
1657         TextClass const & tclass = bparams.getTextClass();
1658
1659         docstring fmt;
1660         if (process_appendix && params().appendix())
1661                 fmt = translateIfPossible(layout->labelstring_appendix(),
1662                         bparams);
1663         else
1664                 fmt = translateIfPossible(layout->labelstring(), bparams);
1665
1666         if (fmt.empty() && layout->labeltype == LABEL_COUNTER 
1667             && !layout->counter.empty())
1668                 fmt = "\\the" + layout->counter;
1669
1670         // handle 'inherited level parts' in 'fmt',
1671         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
1672         size_t const i = fmt.find('@', 0);
1673         if (i != docstring::npos) {
1674                 size_t const j = fmt.find('@', i + 1);
1675                 if (j != docstring::npos) {
1676                         docstring parent(fmt, i + 1, j - i - 1);
1677                         docstring label = expandLabel(tclass[parent], bparams,
1678                                                       process_appendix);
1679                         fmt = docstring(fmt, 0, i) + label 
1680                                 + docstring(fmt, j + 1, docstring::npos);
1681                 }
1682         }
1683
1684         return tclass.counters().counterLabel(fmt);
1685 }
1686
1687
1688 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1689 {
1690         layout(new_layout);
1691         LyXAlignment const oldAlign = params().align();
1692         
1693         if (!(oldAlign & layout()->alignpossible)) {
1694                 frontend::Alert::warning(_("Alignment not permitted"), 
1695                         _("The new layout does not permit the alignment previously used.\nSetting to default."));
1696                 params().align(LYX_ALIGN_LAYOUT);
1697         }
1698 }
1699
1700
1701 pos_type Paragraph::beginOfBody() const
1702 {
1703         return begin_of_body_;
1704 }
1705
1706
1707 void Paragraph::setBeginOfBody()
1708 {
1709         if (layout()->labeltype != LABEL_MANUAL) {
1710                 begin_of_body_ = 0;
1711                 return;
1712         }
1713
1714         // Unroll the first two cycles of the loop
1715         // and remember the previous character to
1716         // remove unnecessary getChar() calls
1717         pos_type i = 0;
1718         pos_type end = size();
1719         if (i < end && !isNewline(i)) {
1720                 ++i;
1721                 char_type previous_char = 0;
1722                 char_type temp = 0;
1723                 if (i < end) {
1724                         previous_char = text_[i];
1725                         if (!isNewline(i)) {
1726                                 ++i;
1727                                 while (i < end && previous_char != ' ') {
1728                                         temp = text_[i];
1729                                         if (isNewline(i))
1730                                                 break;
1731                                         ++i;
1732                                         previous_char = temp;
1733                                 }
1734                         }
1735                 }
1736         }
1737
1738         begin_of_body_ = i;
1739 }
1740
1741
1742 // returns -1 if inset not found
1743 int Paragraph::getPositionOfInset(Inset const * inset) const
1744 {
1745         // Find the entry.
1746         InsetList::const_iterator it = insetlist.begin();
1747         InsetList::const_iterator end = insetlist.end();
1748         for (; it != end; ++it)
1749                 if (it->inset == inset)
1750                         return it->pos;
1751         return -1;
1752 }
1753
1754
1755 InsetBibitem * Paragraph::bibitem() const
1756 {
1757         if (!insetlist.empty()) {
1758                 Inset * inset = insetlist.begin()->inset;
1759                 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1760                         return static_cast<InsetBibitem *>(inset);
1761         }
1762         return 0;
1763 }
1764
1765
1766 bool Paragraph::forceDefaultParagraphs() const
1767 {
1768         return inInset() && inInset()->forceDefaultParagraphs(0);
1769 }
1770
1771
1772 namespace {
1773
1774 // paragraphs inside floats need different alignment tags to avoid
1775 // unwanted space
1776
1777 bool noTrivlistCentering(Inset::Code code)
1778 {
1779         return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1780 }
1781
1782
1783 string correction(string const & orig)
1784 {
1785         if (orig == "flushleft")
1786                 return "raggedright";
1787         if (orig == "flushright")
1788                 return "raggedleft";
1789         if (orig == "center")
1790                 return "centering";
1791         return orig;
1792 }
1793
1794
1795 string const corrected_env(string const & suffix, string const & env,
1796         Inset::Code code)
1797 {
1798         string output = suffix + "{";
1799         if (noTrivlistCentering(code))
1800                 output += correction(env);
1801         else
1802                 output += env;
1803         output += "}";
1804         if (suffix == "\\begin")
1805                 output += "\n";
1806         return output;
1807 }
1808
1809
1810 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1811 {
1812         if (!contains(str, "\n"))
1813                 column += str.size();
1814         else {
1815                 string tmp;
1816                 texrow.newline();
1817                 column = rsplit(str, tmp, '\n').size();
1818         }
1819 }
1820
1821 } // namespace anon
1822
1823
1824 // This could go to ParagraphParameters if we want to
1825 int Paragraph::startTeXParParams(BufferParams const & bparams,
1826                                  odocstream & os, TexRow & texrow,
1827                                  bool moving_arg) const
1828 {
1829         int column = 0;
1830
1831         if (params().noindent()) {
1832                 os << "\\noindent ";
1833                 column += 10;
1834         }
1835         
1836         LyXAlignment const curAlign = params().align();
1837
1838         if (curAlign == layout()->align)
1839                 return column;
1840
1841         switch (curAlign) {
1842         case LYX_ALIGN_NONE:
1843         case LYX_ALIGN_BLOCK:
1844         case LYX_ALIGN_LAYOUT:
1845         case LYX_ALIGN_SPECIAL:
1846                 break;
1847         case LYX_ALIGN_LEFT:
1848         case LYX_ALIGN_RIGHT:
1849         case LYX_ALIGN_CENTER:
1850                 if (moving_arg) {
1851                         os << "\\protect";
1852                         column += 8;
1853                 }
1854                 break;
1855         }
1856
1857         switch (curAlign) {
1858         case LYX_ALIGN_NONE:
1859         case LYX_ALIGN_BLOCK:
1860         case LYX_ALIGN_LAYOUT:
1861         case LYX_ALIGN_SPECIAL:
1862                 break;
1863         case LYX_ALIGN_LEFT: {
1864                 string output;
1865                 if (getParLanguage(bparams)->babel() != "hebrew")
1866                         output = corrected_env("\\begin", "flushleft", ownerCode());
1867                 else
1868                         output = corrected_env("\\begin", "flushright", ownerCode());
1869                 os << from_ascii(output);
1870                 adjust_row_column(output, texrow, column);
1871                 break;
1872         } case LYX_ALIGN_RIGHT: {
1873                 string output;
1874                 if (getParLanguage(bparams)->babel() != "hebrew")
1875                         output = corrected_env("\\begin", "flushright", ownerCode());
1876                 else
1877                         output = corrected_env("\\begin", "flushleft", ownerCode());
1878                 os << from_ascii(output);
1879                 adjust_row_column(output, texrow, column);
1880                 break;
1881         } case LYX_ALIGN_CENTER: {
1882                 string output;
1883                 output = corrected_env("\\begin", "center", ownerCode());
1884                 os << from_ascii(output);
1885                 adjust_row_column(output, texrow, column);
1886                 break;
1887         }
1888         }
1889
1890         return column;
1891 }
1892
1893
1894 // This could go to ParagraphParameters if we want to
1895 int Paragraph::endTeXParParams(BufferParams const & bparams,
1896                                odocstream & os, TexRow & texrow,
1897                                bool moving_arg) const
1898 {
1899         int column = 0;
1900
1901         switch (params().align()) {
1902         case LYX_ALIGN_NONE:
1903         case LYX_ALIGN_BLOCK:
1904         case LYX_ALIGN_LAYOUT:
1905         case LYX_ALIGN_SPECIAL:
1906                 break;
1907         case LYX_ALIGN_LEFT:
1908         case LYX_ALIGN_RIGHT:
1909         case LYX_ALIGN_CENTER:
1910                 if (moving_arg) {
1911                         os << "\\protect";
1912                         column = 8;
1913                 }
1914                 break;
1915         }
1916
1917         switch (params().align()) {
1918         case LYX_ALIGN_NONE:
1919         case LYX_ALIGN_BLOCK:
1920         case LYX_ALIGN_LAYOUT:
1921         case LYX_ALIGN_SPECIAL:
1922                 break;
1923         case LYX_ALIGN_LEFT: {
1924                 string output;
1925                 if (getParLanguage(bparams)->babel() != "hebrew")
1926                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1927                 else
1928                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1929                 os << from_ascii(output);
1930                 adjust_row_column(output, texrow, column);
1931                 break;
1932         } case LYX_ALIGN_RIGHT: {
1933                 string output;
1934                 if (getParLanguage(bparams)->babel() != "hebrew")
1935                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1936                 else
1937                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1938                 os << from_ascii(output);
1939                 adjust_row_column(output, texrow, column);
1940                 break;
1941         } case LYX_ALIGN_CENTER: {
1942                 string output;
1943                 output = corrected_env("\n\\par\\end", "center", ownerCode());
1944                 os << from_ascii(output);
1945                 adjust_row_column(output, texrow, column);
1946                 break;
1947         }
1948         }
1949
1950         return column;
1951 }
1952
1953
1954 // This one spits out the text of the paragraph
1955 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1956                                 BufferParams const & bparams,
1957                                 Font const & outerfont,
1958                                 odocstream & os, TexRow & texrow,
1959                                 OutputParams const & runparams) const
1960 {
1961         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...     " << this << endl;
1962
1963         bool return_value = false;
1964
1965         Layout_ptr style;
1966
1967         // well we have to check if we are in an inset with unlimited
1968         // length (all in one row) if that is true then we don't allow
1969         // any special options in the paragraph and also we don't allow
1970         // any environment other than the default layout of the text class
1971         // to be valid!
1972         bool asdefault = forceDefaultParagraphs();
1973
1974         if (asdefault) {
1975                 style = bparams.getTextClass().defaultLayout();
1976         } else {
1977                 style = layout();
1978         }
1979
1980         // Current base font for all inherited font changes, without any
1981         // change caused by an individual character, except for the language:
1982         // It is set to the language of the first character.
1983         // As long as we are in the label, this font is the base font of the
1984         // label. Before the first body character it is set to the base font
1985         // of the body.
1986         Font basefont;
1987
1988         // Maybe we have to create a optional argument.
1989         pos_type body_pos = beginOfBody();
1990         unsigned int column = 0;
1991
1992         if (body_pos > 0) {
1993                 // the optional argument is kept in curly brackets in
1994                 // case it contains a ']'
1995                 os << "[{";
1996                 column += 2;
1997                 basefont = getLabelFont(bparams, outerfont);
1998         } else {
1999                 basefont = getLayoutFont(bparams, outerfont);
2000         }
2001
2002         // Which font is currently active?
2003         Font running_font(basefont);
2004         // Do we have an open font change?
2005         bool open_font = false;
2006
2007         Change runningChange = Change(Change::UNCHANGED);
2008
2009         texrow.start(id(), 0);
2010
2011         // if the paragraph is empty, the loop will not be entered at all
2012         if (empty()) {
2013                 if (style->isCommand()) {
2014                         os << '{';
2015                         ++column;
2016                 }
2017                 if (!asdefault)
2018                         column += startTeXParParams(bparams, os, texrow,
2019                                                     runparams.moving_arg);
2020         }
2021
2022         for (pos_type i = 0; i < size(); ++i) {
2023                 // First char in paragraph or after label?
2024                 if (i == body_pos) {
2025                         if (body_pos > 0) {
2026                                 if (open_font) {
2027                                         column += running_font.latexWriteEndChanges(
2028                                                 os, bparams, runparams,
2029                                                 basefont, basefont);
2030                                         open_font = false;
2031                                 }
2032                                 basefont = getLayoutFont(bparams, outerfont);
2033                                 running_font = basefont;
2034
2035                                 column += Changes::latexMarkChange(os, bparams,
2036                                                 runningChange, Change(Change::UNCHANGED));
2037                                 runningChange = Change(Change::UNCHANGED);
2038
2039                                 os << "}] ";
2040                                 column +=3;
2041                         }
2042                         if (style->isCommand()) {
2043                                 os << '{';
2044                                 ++column;
2045                         }
2046
2047                         if (!asdefault)
2048                                 column += startTeXParParams(bparams, os,
2049                                                             texrow,
2050                                                             runparams.moving_arg);
2051                 }
2052
2053                 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2054                                                                  : pimpl_->lookupChange(i);
2055
2056                 if (bparams.outputChanges && runningChange != change) {
2057                         if (open_font) {
2058                                 column += running_font.latexWriteEndChanges(
2059                                                 os, bparams, runparams, basefont, basefont);
2060                                 open_font = false;
2061                         }
2062                         basefont = getLayoutFont(bparams, outerfont);
2063                         running_font = basefont;
2064
2065                         column += Changes::latexMarkChange(os, bparams, runningChange, change);
2066                         runningChange = change;
2067                 }
2068
2069                 // do not output text which is marked deleted
2070                 // if change tracking output is disabled
2071                 if (!bparams.outputChanges && change.type == Change::DELETED) {
2072                         continue;
2073                 }
2074
2075                 ++column;
2076
2077                 value_type const c = getChar(i);
2078
2079                 // Fully instantiated font
2080                 Font const font = getFont(bparams, i, outerfont);
2081
2082                 Font const last_font = running_font;
2083
2084                 // Do we need to close the previous font?
2085                 if (open_font &&
2086                     (font != running_font ||
2087                      font.language() != running_font.language()))
2088                 {
2089                         column += running_font.latexWriteEndChanges(
2090                                         os, bparams, runparams, basefont,
2091                                         (i == body_pos-1) ? basefont : font);
2092                         running_font = basefont;
2093                         open_font = false;
2094                 }
2095
2096                 // Switch file encoding if necessary
2097                 if (runparams.encoding->package() == Encoding::inputenc &&
2098                     font.language()->encoding()->package() == Encoding::inputenc) {
2099                         std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2100                                         runparams.moving_arg, *(runparams.encoding),
2101                                         *(font.language()->encoding()));
2102                         if (enc_switch.first) {
2103                                 column += enc_switch.second;
2104                                 runparams.encoding = font.language()->encoding();
2105                         }
2106                 }
2107
2108                 // Do we need to change font?
2109                 if ((font != running_font ||
2110                      font.language() != running_font.language()) &&
2111                         i != body_pos - 1)
2112                 {
2113                         odocstringstream ods;
2114                         column += font.latexWriteStartChanges(ods, bparams,
2115                                                               runparams, basefont,
2116                                                               last_font);
2117                         running_font = font;
2118                         open_font = true;
2119                         docstring fontchange = ods.str();
2120                         // check if the fontchange ends with a trailing blank
2121                         // (like "\small " (see bug 3382)
2122                         if (suffixIs(fontchange, ' ') && c == ' ')
2123                                 os << fontchange.substr(0, fontchange.size() - 1) 
2124                                    << from_ascii("{}");
2125                         else
2126                                 os << fontchange;
2127                 }
2128
2129                 if (c == ' ') {
2130                         // Do not print the separation of the optional argument
2131                         // if style->pass_thru is false. This works because
2132                         // simpleTeXSpecialChars ignores spaces if
2133                         // style->pass_thru is false.
2134                         if (i != body_pos - 1) {
2135                                 if (pimpl_->simpleTeXBlanks(
2136                                                 *(runparams.encoding), os, texrow,
2137                                                 i, column, font, *style))
2138                                         // A surrogate pair was output. We
2139                                         // must not call simpleTeXSpecialChars
2140                                         // in this iteration, since
2141                                         // simpleTeXBlanks incremented i, and
2142                                         // simpleTeXSpecialChars would output
2143                                         // the combining character again.
2144                                         continue;
2145                         }
2146                 }
2147
2148                 OutputParams rp = runparams;
2149                 rp.free_spacing = style->free_spacing;
2150                 rp.local_font = &font;
2151                 rp.intitle = style->intitle;
2152                 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2153                                         texrow, rp, running_font,
2154                                         basefont, outerfont, open_font,
2155                                         runningChange, *style, i, column, c);
2156
2157                 // Set the encoding to that returned from simpleTeXSpecialChars (see
2158                 // comment for encoding member in OutputParams.h)
2159                 runparams.encoding = rp.encoding;
2160         }
2161
2162         // If we have an open font definition, we have to close it
2163         if (open_font) {
2164 #ifdef FIXED_LANGUAGE_END_DETECTION
2165                 if (next_) {
2166                         running_font
2167                                 .latexWriteEndChanges(os, bparams, runparams,
2168                                         basefont,
2169                                         next_->getFont(bparams, 0, outerfont));
2170                 } else {
2171                         running_font.latexWriteEndChanges(os, bparams,
2172                                         runparams, basefont, basefont);
2173                 }
2174 #else
2175 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2176 //FIXME: there as we start another \selectlanguage with the next paragraph if
2177 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2178                 running_font.latexWriteEndChanges(os, bparams, runparams,
2179                                 basefont, basefont);
2180 #endif
2181         }
2182
2183         column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2184
2185         // Needed if there is an optional argument but no contents.
2186         if (body_pos > 0 && body_pos == size()) {
2187                 os << "}]~";
2188                 return_value = false;
2189         }
2190
2191         if (!asdefault) {
2192                 column += endTeXParParams(bparams, os, texrow,
2193                                           runparams.moving_arg);
2194         }
2195
2196         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2197         return return_value;
2198 }
2199
2200
2201 namespace {
2202
2203 enum PAR_TAG {
2204         PAR_NONE=0,
2205         TT = 1,
2206         SF = 2,
2207         BF = 4,
2208         IT = 8,
2209         SL = 16,
2210         EM = 32
2211 };
2212
2213
2214 string tag_name(PAR_TAG const & pt) {
2215         switch (pt) {
2216         case PAR_NONE: return "!-- --";
2217         case TT: return "tt";
2218         case SF: return "sf";
2219         case BF: return "bf";
2220         case IT: return "it";
2221         case SL: return "sl";
2222         case EM: return "em";
2223         }
2224         return "";
2225 }
2226
2227
2228 inline
2229 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2230 {
2231         p1 = static_cast<PAR_TAG>(p1 | p2);
2232 }
2233
2234
2235 inline
2236 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2237 {
2238         p1 = static_cast<PAR_TAG>(p1 & ~p2);
2239 }
2240
2241 } // anon
2242
2243
2244 bool Paragraph::emptyTag() const
2245 {
2246         for (pos_type i = 0; i < size(); ++i) {
2247                 if (isInset(i)) {
2248                         Inset const * inset = getInset(i);
2249                         Inset::Code lyx_code = inset->lyxCode();
2250                         if (lyx_code != Inset::TOC_CODE &&
2251                             lyx_code != Inset::INCLUDE_CODE &&
2252                             lyx_code != Inset::GRAPHICS_CODE &&
2253                             lyx_code != Inset::ERT_CODE &&
2254                             lyx_code != Inset::LISTINGS_CODE &&
2255                             lyx_code != Inset::FLOAT_CODE &&
2256                             lyx_code != Inset::TABULAR_CODE) {
2257                                 return false;
2258                         }
2259                 } else {
2260                         value_type c = getChar(i);
2261                         if (c != ' ' && c != '\t')
2262                                 return false;
2263                 }
2264         }
2265         return true;
2266 }
2267
2268
2269 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2270 {
2271         for (pos_type i = 0; i < size(); ++i) {
2272                 if (isInset(i)) {
2273                         Inset const * inset = getInset(i);
2274                         Inset::Code lyx_code = inset->lyxCode();
2275                         if (lyx_code == Inset::LABEL_CODE) {
2276                                 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2277                                 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2278                         }
2279                 }
2280
2281         }
2282         return string();
2283 }
2284
2285
2286 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2287 {
2288         pos_type i;
2289         for (i = 0; i < size(); ++i) {
2290                 if (isInset(i)) {
2291                         Inset const * inset = getInset(i);
2292                         inset->docbook(buf, os, runparams);
2293                 } else {
2294                         value_type c = getChar(i);
2295                         if (c == ' ')
2296                                 break;
2297                         os << sgml::escapeChar(c);
2298                 }
2299         }
2300         return i;
2301 }
2302
2303
2304 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2305 {
2306         Font font_old;
2307
2308         for (pos_type i = initial; i < size(); ++i) {
2309                 Font font = getFont(buf.params(), i, outerfont);
2310                 if (isInset(i))
2311                         return false;
2312                 if (i != initial && font != font_old)
2313                         return false;
2314                 font_old = font;
2315         }
2316
2317         return true;
2318 }
2319
2320
2321 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2322                                     odocstream & os,
2323                                     OutputParams const & runparams,
2324                                     Font const & outerfont,
2325                                     pos_type initial) const
2326 {
2327         bool emph_flag = false;
2328
2329         Layout_ptr const & style = layout();
2330         Font font_old =
2331                 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2332
2333         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2334                 os << "]]>";
2335
2336         // parsing main loop
2337         for (pos_type i = initial; i < size(); ++i) {
2338                 Font font = getFont(buf.params(), i, outerfont);
2339
2340                 // handle <emphasis> tag
2341                 if (font_old.emph() != font.emph()) {
2342                         if (font.emph() == Font::ON) {
2343                                 os << "<emphasis>";
2344                                 emph_flag = true;
2345                         } else if (i != initial) {
2346                                 os << "</emphasis>";
2347                                 emph_flag = false;
2348                         }
2349                 }
2350
2351                 if (isInset(i)) {
2352                         Inset const * inset = getInset(i);
2353                         inset->docbook(buf, os, runparams);
2354                 } else {
2355                         value_type c = getChar(i);
2356
2357                         if (style->pass_thru)
2358                                 os.put(c);
2359                         else
2360                                 os << sgml::escapeChar(c);
2361                 }
2362                 font_old = font;
2363         }
2364
2365         if (emph_flag) {
2366                 os << "</emphasis>";
2367         }
2368
2369         if (style->free_spacing)
2370                 os << '\n';
2371         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2372                 os << "<![CDATA[";
2373 }
2374
2375
2376 bool Paragraph::isNewline(pos_type pos) const
2377 {
2378         return isInset(pos)
2379                 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2380 }
2381
2382
2383 bool Paragraph::isLineSeparator(pos_type pos) const
2384 {
2385         value_type const c = getChar(pos);
2386         return isLineSeparatorChar(c)
2387                 || (c == Paragraph::META_INSET && getInset(pos) &&
2388                 getInset(pos)->isLineSeparator());
2389 }
2390
2391
2392 /// Used by the spellchecker
2393 bool Paragraph::isLetter(pos_type pos) const
2394 {
2395         if (isInset(pos))
2396                 return getInset(pos)->isLetter();
2397         else {
2398                 value_type const c = getChar(pos);
2399                 return isLetterChar(c) || isDigit(c);
2400         }
2401 }
2402
2403
2404 Language const *
2405 Paragraph::getParLanguage(BufferParams const & bparams) const
2406 {
2407         if (!empty())
2408                 return getFirstFontSettings(bparams).language();
2409         // FIXME: we should check the prev par as well (Lgb)
2410         return bparams.language;
2411 }
2412
2413
2414 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2415 {
2416         return lyxrc.rtl_support
2417                 && getParLanguage(bparams)->rightToLeft()
2418                 && ownerCode() != Inset::ERT_CODE
2419                 && ownerCode() != Inset::LISTINGS_CODE;
2420 }
2421
2422
2423 void Paragraph::changeLanguage(BufferParams const & bparams,
2424                                Language const * from, Language const * to)
2425 {
2426         // change language including dummy font change at the end
2427         for (pos_type i = 0; i <= size(); ++i) {
2428                 Font font = getFontSettings(bparams, i);
2429                 if (font.language() == from) {
2430                         font.setLanguage(to);
2431                         setFont(i, font);
2432                 }
2433         }
2434 }
2435
2436
2437 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2438 {
2439         Language const * doc_language = bparams.language;
2440         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2441         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2442
2443         for (; cit != end; ++cit)
2444                 if (cit->font().language() != ignore_language &&
2445                     cit->font().language() != latex_language &&
2446                     cit->font().language() != doc_language)
2447                         return true;
2448         return false;
2449 }
2450
2451
2452 // Convert the paragraph to a string.
2453 // Used for building the table of contents
2454 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2455 {
2456         return asString(buffer, 0, size(), label);
2457 }
2458
2459
2460 docstring const Paragraph::asString(Buffer const & buffer,
2461                                  pos_type beg, pos_type end, bool label) const
2462 {
2463
2464         odocstringstream os;
2465
2466         if (beg == 0 && label && !params().labelString().empty())
2467                 os << params().labelString() << ' ';
2468
2469         for (pos_type i = beg; i < end; ++i) {
2470                 value_type const c = getChar(i);
2471                 if (isPrintable(c))
2472                         os.put(c);
2473                 else if (c == META_INSET)
2474                         getInset(i)->textString(buffer, os);
2475         }
2476
2477         return os.str();
2478 }
2479
2480
2481 void Paragraph::setInsetOwner(Inset * inset)
2482 {
2483         pimpl_->inset_owner = inset;
2484 }
2485
2486
2487 Change const & Paragraph::lookupChange(pos_type pos) const
2488 {
2489         BOOST_ASSERT(pos <= size());
2490         return pimpl_->lookupChange(pos);
2491 }
2492
2493
2494 bool Paragraph::isChanged(pos_type start, pos_type end) const
2495 {
2496         return pimpl_->isChanged(start, end);
2497 }
2498
2499
2500 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2501 {
2502         return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2503 }
2504
2505
2506 void Paragraph::setChange(Change const & change)
2507 {
2508         pimpl_->setChange(change);
2509 }
2510
2511
2512 void Paragraph::setChange(pos_type pos, Change const & change)
2513 {
2514         pimpl_->setChange(pos, change);
2515 }
2516
2517
2518 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2519 {
2520         return pimpl_->acceptChanges(bparams, start, end);
2521 }
2522
2523
2524 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2525 {
2526         return pimpl_->rejectChanges(bparams, start, end);
2527 }
2528
2529
2530 int Paragraph::id() const
2531 {
2532         return pimpl_->id_;
2533 }
2534
2535
2536 Layout_ptr const & Paragraph::layout() const
2537 {
2538         return layout_;
2539 }
2540
2541
2542 void Paragraph::layout(Layout_ptr const & new_layout)
2543 {
2544         layout_ = new_layout;
2545 }
2546
2547
2548 Inset * Paragraph::inInset() const
2549 {
2550         return pimpl_->inset_owner;
2551 }
2552
2553
2554 Inset::Code Paragraph::ownerCode() const
2555 {
2556         return pimpl_->inset_owner
2557                 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2558 }
2559
2560
2561 ParagraphParameters & Paragraph::params()
2562 {
2563         return pimpl_->params;
2564 }
2565
2566
2567 ParagraphParameters const & Paragraph::params() const
2568 {
2569         return pimpl_->params;
2570 }
2571
2572
2573 bool Paragraph::isFreeSpacing() const
2574 {
2575         if (layout()->free_spacing)
2576                 return true;
2577
2578         // for now we just need this, later should we need this in some
2579         // other way we can always add a function to Inset too.
2580         return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2581 }
2582
2583
2584 bool Paragraph::allowEmpty() const
2585 {
2586         if (layout()->keepempty)
2587                 return true;
2588         return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2589 }
2590
2591
2592 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2593 {
2594         if (!Encodings::is_arabic(c))
2595                 return c;
2596
2597         value_type prev_char = ' ';
2598         value_type next_char = ' ';
2599
2600         for (pos_type i = pos - 1; i >= 0; --i) {
2601                 value_type const par_char = getChar(i);
2602                 if (!Encodings::isComposeChar_arabic(par_char)) {
2603                         prev_char = par_char;
2604                         break;
2605                 }
2606         }
2607
2608         for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2609                 value_type const par_char = getChar(i);
2610                 if (!Encodings::isComposeChar_arabic(par_char)) {
2611                         next_char = par_char;
2612                         break;
2613                 }
2614         }
2615
2616         if (Encodings::is_arabic(next_char)) {
2617                 if (Encodings::is_arabic(prev_char) &&
2618                         !Encodings::is_arabic_special(prev_char))
2619                         return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2620                 else
2621                         return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2622         } else {
2623                 if (Encodings::is_arabic(prev_char) &&
2624                         !Encodings::is_arabic_special(prev_char))
2625                         return Encodings::transformChar(c, Encodings::FORM_FINAL);
2626                 else
2627                         return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2628         }
2629 }
2630
2631
2632 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2633 {
2634         if (!isHfill(pos))
2635                 return false;
2636
2637         BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2638
2639         // expand at the end of a row only if there is another hfill on the same row
2640         if (pos == row.endpos() - 1) {
2641                 for (pos_type i = row.pos(); i < pos; i++) {
2642                         if (isHfill(i))
2643                                 return true;
2644                 }
2645                 return false;
2646         }
2647
2648         // expand at the beginning of a row only if it is the first row of a paragraph
2649         if (pos == row.pos()) {
2650                 return pos == 0;
2651         }
2652
2653         // do not expand in some labels
2654         if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2655                 return false;
2656
2657         // if there is anything between the first char of the row and
2658         // the specified position that is neither a newline nor an hfill,
2659         // the hfill will be expanded, otherwise it won't
2660         for (pos_type i = row.pos(); i < pos; i++) {
2661                 if (!isNewline(i) && !isHfill(i))
2662                         return true;
2663         }
2664         return false;
2665 }
2666
2667
2668 int Paragraph::checkBiblio(bool track_changes)
2669 {
2670         //FIXME From JS:
2671         //This is getting more and more a mess. ...We really should clean
2672         //up this bibitem issue for 1.6. See also bug 2743.
2673
2674         // Add bibitem insets if necessary
2675         if (layout()->labeltype != LABEL_BIBLIO)
2676                 return 0;
2677
2678         bool hasbibitem = !insetlist.empty()
2679                 // Insist on it being in pos 0
2680                 && getChar(0) == Paragraph::META_INSET
2681                 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2682
2683         docstring oldkey;
2684         docstring oldlabel;
2685
2686         // remove a bibitem in pos != 0
2687         // restore it later in pos 0 if necessary
2688         // (e.g. if a user inserts contents _before_ the item)
2689         // we're assuming there's only one of these, which there
2690         // should be.
2691         int erasedInsetPosition = -1;
2692         InsetList::iterator it = insetlist.begin();
2693         InsetList::iterator end = insetlist.end();
2694         for (; it != end; ++it)
2695                 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2696                     && it->pos > 0) {
2697                         InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2698                         oldkey = olditem->getParam("key");
2699                         oldlabel = olditem->getParam("label");
2700                         erasedInsetPosition = it->pos;
2701                         eraseChar(erasedInsetPosition, track_changes);
2702                         break;
2703         }
2704
2705         //There was an InsetBibitem at the beginning, and we didn't
2706         //have to erase one.
2707         if (hasbibitem && erasedInsetPosition < 0)
2708                         return 0;
2709
2710         //There was an InsetBibitem at the beginning and we did have to
2711         //erase one. So we give its properties to the beginning inset.
2712         if (hasbibitem) {
2713                 InsetBibitem * inset =
2714                         static_cast<InsetBibitem *>(insetlist.begin()->inset);
2715                 if (!oldkey.empty())
2716                         inset->setParam("key", oldkey);
2717                 inset->setParam("label", oldlabel);
2718                 return -erasedInsetPosition;
2719         }
2720
2721         //There was no inset at the beginning, so we need to create one with
2722         //the key and label of the one we erased.
2723         InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2724         // restore values of previously deleted item in this par.
2725         if (!oldkey.empty())
2726                 inset->setParam("key", oldkey);
2727         inset->setParam("label", oldlabel);
2728         insertInset(0, static_cast<Inset *>(inset),
2729                     Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2730
2731         return 1;
2732 }
2733
2734
2735 void Paragraph::checkAuthors(AuthorList const & authorList)
2736 {
2737         pimpl_->changes_.checkAuthors(authorList);
2738 }
2739
2740 } // namespace lyx