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