]> git.lyx.org Git - lyx.git/blob - src/Paragraph.cpp
Cosmetics.
[lyx.git] / src / Paragraph.cpp
1 /**
2  * \file Paragraph.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author Angus Leeming
10  * \author John Levon
11  * \author André Pönitz
12  * \author Dekel Tsur
13  * \author Jürgen Vigna
14  *
15  * Full author contact details are available in file CREDITS.
16  */
17
18 #include <config.h>
19
20 #include "Paragraph.h"
21
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "Counters.h"
25 #include "Encoding.h"
26 #include "debug.h"
27 #include "gettext.h"
28 #include "Language.h"
29 #include "LaTeXFeatures.h"
30 #include "Color.h"
31 #include "Layout.h"
32 #include "Length.h"
33 #include "Font.h"
34 #include "LyXRC.h"
35 #include "Messages.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "paragraph_funcs.h"
39 #include "ParagraphParameters.h"
40 #include "sgml.h"
41 #include "TexRow.h"
42 #include "VSpace.h"
43
44 #include "frontends/alert.h"
45 #include "frontends/FontMetrics.h"
46
47 #include "insets/InsetBibitem.h"
48 #include "insets/InsetOptArg.h"
49
50 #include "support/lstrings.h"
51 #include "support/textutils.h"
52 #include "support/convert.h"
53 #include "support/unicode.h"
54
55 #include <boost/bind.hpp>
56 #include <boost/next_prior.hpp>
57
58 #include <algorithm>
59 #include <sstream>
60
61 using std::distance;
62 using std::endl;
63 using std::string;
64 using std::ostream;
65
66 namespace lyx {
67
68 using support::contains;
69 using support::suffixIs;
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 &,
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 & 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 (lookupChange(i).type == Change::DELETED) {
729                         if( ++runparams.inDeletedInset == 1)
730                                 runparams.changeOfDeletedInset = lookupChange(i);
731                 }
732
733                 if (inset->canTrackChanges()) {
734                         column += Changes::latexMarkChange(os, bparams, running_change,
735                                 Change(Change::UNCHANGED));
736                         running_change = Change(Change::UNCHANGED);
737                 }
738
739                 bool close = false;
740                 odocstream::pos_type const len = os.tellp();
741
742                 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
743                      || inset->lyxCode() == Inset::MATH_CODE
744                      || inset->lyxCode() == Inset::URL_CODE)
745                     && running_font.isRightToLeft()) {
746                         if (running_font.language()->lang() == "farsi")
747                                 os << "\\beginL{}";
748                         else
749                                 os << "\\L{";
750                         close = true;
751                 }
752
753 // FIXME: Bug: we can have an empty font change here!
754 // if there has just been a font change, we are going to close it
755 // right now, which means stupid latex code like \textsf{}. AFAIK,
756 // this does not harm dvi output. A minor bug, thus (JMarc)
757                 // Some insets cannot be inside a font change command.
758                 // However, even such insets *can* be placed in \L or \R
759                 // or their equivalents (for RTL language switches), so we don't
760                 // close the language in those cases.
761                 // ArabTeX, though, cannot handle this special behavior, it seems.
762                 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
763                                            running_font.language()->lang() == "arabic_arabtex";
764                 if (open_font && inset->noFontChange()) {
765                         bool closeLanguage = arabtex ||
766                                 basefont.isRightToLeft() == running_font.isRightToLeft();
767                         unsigned int count = running_font.latexWriteEndChanges(
768                                         os, bparams, runparams,
769                                                 basefont, basefont, closeLanguage);
770                         column += count;
771                         // if any font properties were closed, update the running_font, 
772                         // making sure, however, to leave the language as it was
773                         if (count > 0) {
774                                 // FIXME: probably a better way to keep track of the old 
775                                 // language, than copying the entire font?
776                                 Font const copy_font(running_font);
777                                 basefont = owner_->getLayoutFont(bparams, outerfont);
778                                 running_font = basefont;
779                                 if (!closeLanguage)
780                                         running_font.setLanguage(copy_font.language());
781                                 // leave font open if language is still open
782                                 open_font = (running_font.language() == basefont.language());
783                                 if (closeLanguage)
784                                         runparams.local_font = &basefont;
785                         }
786                 }
787
788                 int tmp = inset->latex(buf, os, runparams);
789
790                 if (close) {
791                         if (running_font.language()->lang() == "farsi")
792                                 os << "\\endL{}";
793                         else
794                                 os << '}';
795                 }
796
797                 if (tmp) {
798                         for (int j = 0; j < tmp; ++j) {
799                                 texrow.newline();
800                         }
801                         texrow.start(owner_->id(), i + 1);
802                         column = 0;
803                 } else {
804                         column += os.tellp() - len;
805                 }
806
807                 if (lookupChange(i).type == Change::DELETED) {
808                         --runparams.inDeletedInset;
809                 }
810         }
811         break;
812
813         default:
814                 // And now for the special cases within each mode
815
816                 switch (c) {
817                 case '\\':
818                         os << "\\textbackslash{}";
819                         column += 15;
820                         break;
821
822                 case '|': case '<': case '>':
823                         // In T1 encoding, these characters exist
824                         if (lyxrc.fontenc == "T1") {
825                                 os.put(c);
826                                 //... but we should avoid ligatures
827                                 if ((c == '>' || c == '<')
828                                     && i <= size() - 2
829                                     && getChar(i + 1) == c) {
830                                         //os << "\\textcompwordmark{}";
831                                         //column += 19;
832                                         // Jean-Marc, have a look at
833                                         // this. I think this works
834                                         // equally well:
835                                         os << "\\,{}";
836                                         // Lgb
837                                         column += 3;
838                                 }
839                                 break;
840                         }
841                         // Typewriter font also has them
842                         if (running_font.family() == Font::TYPEWRITER_FAMILY) {
843                                 os.put(c);
844                                 break;
845                         }
846                         // Otherwise, we use what LaTeX
847                         // provides us.
848                         switch (c) {
849                         case '<':
850                                 os << "\\textless{}";
851                                 column += 10;
852                                 break;
853                         case '>':
854                                 os << "\\textgreater{}";
855                                 column += 13;
856                                 break;
857                         case '|':
858                                 os << "\\textbar{}";
859                                 column += 9;
860                                 break;
861                         }
862                         break;
863
864                 case '-': // "--" in Typewriter mode -> "-{}-"
865                         if (i <= size() - 2 &&
866                             getChar(i + 1) == '-' &&
867                             running_font.family() == Font::TYPEWRITER_FAMILY) {
868                                 os << "-{}";
869                                 column += 2;
870                         } else {
871                                 os << '-';
872                         }
873                         break;
874
875                 case '\"':
876                         os << "\\char`\\\"{}";
877                         column += 9;
878                         break;
879
880                 case '$': case '&':
881                 case '%': case '#': case '{':
882                 case '}': case '_':
883                         os << '\\';
884                         os.put(c);
885                         column += 1;
886                         break;
887
888                 case '~':
889                         os << "\\textasciitilde{}";
890                         column += 16;
891                         break;
892
893                 case '^':
894                         os << "\\textasciicircum{}";
895                         column += 17;
896                         break;
897
898                 case '*': case '[':
899                         // avoid being mistaken for optional arguments
900                         os << '{';
901                         os.put(c);
902                         os << '}';
903                         column += 2;
904                         break;
905
906                 case ' ':
907                         // Blanks are printed before font switching.
908                         // Sure? I am not! (try nice-latex)
909                         // I am sure it's correct. LyX might be smarter
910                         // in the future, but for now, nothing wrong is
911                         // written. (Asger)
912                         break;
913
914                 default:
915
916                         // I assume this is hack treating typewriter as verbatim
917                         // FIXME UNICODE: This can fail if c cannot be encoded
918                         // in the current encoding.
919                         if (running_font.family() == Font::TYPEWRITER_FAMILY) {
920                                 if (c != '\0') {
921                                         os.put(c);
922                                 }
923                                 break;
924                         }
925
926                         // LyX, LaTeX etc.
927
928                         // FIXME: if we have "LaTeX" with a font
929                         // change in the middle (before the 'T', then
930                         // the "TeX" part is still special cased.
931                         // Really we should only operate this on
932                         // "words" for some definition of word
933
934                         size_t pnr = 0;
935
936                         for (; pnr < phrases_nr; ++pnr) {
937                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
938                                         os << special_phrases[pnr].macro;
939                                         i += special_phrases[pnr].phrase.length() - 1;
940                                         column += special_phrases[pnr].macro.length() - 1;
941                                         break;
942                                 }
943                         }
944
945                         if (pnr == phrases_nr && c != '\0') {
946                                 Encoding const & encoding = *(runparams.encoding);
947                                 if (i < size() - 1) {
948                                         char_type next = getChar(i + 1);
949                                         if (Encodings::isCombiningChar(next)) {
950                                                 column += latexSurrogatePair(os, c, next, encoding) - 1;
951                                                 ++i;
952                                                 break;
953                                         }
954                                 }
955                                 docstring const latex = encoding.latexChar(c);
956                                 if (latex.length() > 1 &&
957                                     latex[latex.length() - 1] != '}') {
958                                         // Prevent eating of a following
959                                         // space or command corruption by
960                                         // following characters
961                                         column += latex.length() + 1;
962                                         os << latex << "{}";
963                                 } else {
964                                         column += latex.length() - 1;
965                                         os << latex;
966                                 }
967                         }
968                         break;
969                 }
970         }
971 }
972
973
974 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
975                                 Layout const & layout) const
976 {
977         BufferParams const & bparams = features.bufferParams();
978
979         // check the params.
980         if (!params.spacing().isDefault())
981                 features.require("setspace");
982
983         // then the layouts
984         features.useLayout(layout.name());
985
986         // then the fonts
987         Language const * doc_language = bparams.language;
988
989         FontList::const_iterator fcit = fontlist.begin();
990         FontList::const_iterator fend = fontlist.end();
991         for (; fcit != fend; ++fcit) {
992                 if (fcit->font().noun() == Font::ON) {
993                         LYXERR(Debug::LATEX) << "font.noun: "
994                                              << fcit->font().noun()
995                                              << endl;
996                         features.require("noun");
997                         LYXERR(Debug::LATEX) << "Noun enabled. Font: "
998                                              << to_utf8(fcit->font().stateText(0))
999                                              << endl;
1000                 }
1001                 switch (fcit->font().color()) {
1002                 case Color::none:
1003                 case Color::inherit:
1004                 case Color::ignore:
1005                         // probably we should put here all interface colors used for
1006                         // font displaying! For now I just add this ones I know of (Jug)
1007                 case Color::latex:
1008                 case Color::note:
1009                         break;
1010                 default:
1011                         features.require("color");
1012                         LYXERR(Debug::LATEX) << "Color enabled. Font: "
1013                                              << to_utf8(fcit->font().stateText(0))
1014                                              << endl;
1015                 }
1016
1017                 Language const * language = fcit->font().language();
1018                 if (language->babel() != doc_language->babel() &&
1019                     language != ignore_language &&
1020                     language != latex_language)
1021                 {
1022                         features.useLanguage(language);
1023                         LYXERR(Debug::LATEX) << "Found language "
1024                                              << language->lang() << endl;
1025                 }
1026         }
1027
1028         if (!params.leftIndent().zero())
1029                 features.require("ParagraphLeftIndent");
1030
1031         // then the insets
1032         InsetList::const_iterator icit = owner_->insetlist.begin();
1033         InsetList::const_iterator iend = owner_->insetlist.end();
1034         for (; icit != iend; ++icit) {
1035                 if (icit->inset) {
1036                         icit->inset->validate(features);
1037                         if (layout.needprotect &&
1038                             icit->inset->lyxCode() == Inset::FOOT_CODE)
1039                                 features.require("NeedLyXFootnoteCode");
1040                 }
1041         }
1042
1043         // then the contents
1044         for (pos_type i = 0; i < size() ; ++i) {
1045                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1046                         if (!special_phrases[pnr].builtin
1047                             && isTextAt(special_phrases[pnr].phrase, i)) {
1048                                 features.require(special_phrases[pnr].phrase);
1049                                 break;
1050                         }
1051                 }
1052                 Encodings::validate(getChar(i), features);
1053         }
1054 }
1055
1056
1057 } // namespace lyx
1058
1059
1060 /////////////////////////////////////////////////////////////////////
1061 //
1062 // Paragraph
1063 //
1064 /////////////////////////////////////////////////////////////////////
1065
1066 namespace lyx {
1067
1068 Paragraph::Paragraph()
1069         : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1070 {
1071         itemdepth = 0;
1072         params().clear();
1073 }
1074
1075
1076 Paragraph::Paragraph(Paragraph const & par)
1077         : itemdepth(par.itemdepth), insetlist(par.insetlist),
1078         layout_(par.layout_),
1079         text_(par.text_), begin_of_body_(par.begin_of_body_),
1080         pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1081 {
1082         //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1083         InsetList::iterator it = insetlist.begin();
1084         InsetList::iterator end = insetlist.end();
1085         for (; it != end; ++it)
1086                 it->inset = it->inset->clone();
1087 }
1088
1089
1090 Paragraph & Paragraph::operator=(Paragraph const & par)
1091 {
1092         // needed as we will destroy the pimpl_ before copying it
1093         if (&par != this) {
1094                 itemdepth = par.itemdepth;
1095
1096                 insetlist = par.insetlist;
1097                 InsetList::iterator it = insetlist.begin();
1098                 InsetList::iterator end = insetlist.end();
1099                 for (; it != end; ++it)
1100                         it->inset = it->inset->clone();
1101
1102                 layout_ = par.layout();
1103                 text_ = par.text_;
1104                 begin_of_body_ = par.begin_of_body_;
1105
1106                 delete pimpl_;
1107                 pimpl_ = new Pimpl(*par.pimpl_, this);
1108         }
1109         return *this;
1110 }
1111
1112
1113 Paragraph::~Paragraph()
1114 {
1115         delete pimpl_;
1116         //
1117         //lyxerr << "Paragraph::paragraph_id = "
1118         //       << Paragraph::paragraph_id << endl;
1119 }
1120
1121
1122 void Paragraph::write(Buffer const & buf, ostream & os,
1123                           BufferParams const & bparams,
1124                           depth_type & dth) const
1125 {
1126         // The beginning or end of a deeper (i.e. nested) area?
1127         if (dth != params().depth()) {
1128                 if (params().depth() > dth) {
1129                         while (params().depth() > dth) {
1130                                 os << "\n\\begin_deeper";
1131                                 ++dth;
1132                         }
1133                 } else {
1134                         while (params().depth() < dth) {
1135                                 os << "\n\\end_deeper";
1136                                 --dth;
1137                         }
1138                 }
1139         }
1140
1141         // First write the layout
1142         os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1143
1144         params().write(os);
1145
1146         Font font1(Font::ALL_INHERIT, bparams.language);
1147
1148         Change running_change = Change(Change::UNCHANGED);
1149
1150         int column = 0;
1151         for (pos_type i = 0; i <= size(); ++i) {
1152
1153                 Change change = pimpl_->lookupChange(i);
1154                 Changes::lyxMarkChange(os, column, running_change, change);
1155                 running_change = change;
1156
1157                 if (i == size())
1158                         break;
1159
1160                 // Write font changes
1161                 Font font2 = getFontSettings(bparams, i);
1162                 if (font2 != font1) {
1163                         font2.lyxWriteChanges(font1, os);
1164                         column = 0;
1165                         font1 = font2;
1166                 }
1167
1168                 value_type const c = getChar(i);
1169                 switch (c) {
1170                 case META_INSET:
1171                 {
1172                         Inset const * inset = getInset(i);
1173                         if (inset)
1174                                 if (inset->directWrite()) {
1175                                         // international char, let it write
1176                                         // code directly so it's shorter in
1177                                         // the file
1178                                         inset->write(buf, os);
1179                                 } else {
1180                                         if (i)
1181                                                 os << '\n';
1182                                         os << "\\begin_inset ";
1183                                         inset->write(buf, os);
1184                                         os << "\n\\end_inset\n\n";
1185                                         column = 0;
1186                                 }
1187                 }
1188                 break;
1189                 case '\\':
1190                         os << "\n\\backslash\n";
1191                         column = 0;
1192                         break;
1193                 case '.':
1194                         if (i + 1 < size() && getChar(i + 1) == ' ') {
1195                                 os << ".\n";
1196                                 column = 0;
1197                         } else
1198                                 os << '.';
1199                         break;
1200                 default:
1201                         if ((column > 70 && c == ' ')
1202                             || column > 79) {
1203                                 os << '\n';
1204                                 column = 0;
1205                         }
1206                         // this check is to amend a bug. LyX sometimes
1207                         // inserts '\0' this could cause problems.
1208                         if (c != '\0') {
1209                                 std::vector<char> tmp = ucs4_to_utf8(c);
1210                                 tmp.push_back('\0');
1211                                 os << &tmp[0];
1212                         } else
1213                                 lyxerr << "ERROR (Paragraph::writeFile):"
1214                                         " NULL char in structure." << endl;
1215                         ++column;
1216                         break;
1217                 }
1218         }
1219
1220         os << "\n\\end_layout\n";
1221 }
1222
1223
1224 void Paragraph::validate(LaTeXFeatures & features) const
1225 {
1226         pimpl_->validate(features, *layout());
1227 }
1228
1229
1230 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1231 {
1232         return pimpl_->eraseChar(pos, trackChanges);
1233 }
1234
1235
1236 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1237 {
1238         return pimpl_->eraseChars(start, end, trackChanges);
1239 }
1240
1241
1242 void Paragraph::insert(pos_type start, docstring const & str,
1243                        Font const & font, Change const & change)
1244 {
1245         for (size_t i = 0, n = str.size(); i != n ; ++i)
1246                 insertChar(start + i, str[i], font, change);
1247 }
1248
1249
1250 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1251                            bool trackChanges)
1252 {
1253         pimpl_->insertChar(pos, c, Change(trackChanges ?
1254                            Change::INSERTED : Change::UNCHANGED));
1255 }
1256
1257
1258 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1259                            Font const & font, bool trackChanges)
1260 {
1261         pimpl_->insertChar(pos, c, Change(trackChanges ?
1262                            Change::INSERTED : Change::UNCHANGED));
1263         setFont(pos, font);
1264 }
1265
1266
1267 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1268                            Font const & font, Change const & change)
1269 {
1270         pimpl_->insertChar(pos, c, change);
1271         setFont(pos, font);
1272 }
1273
1274
1275 void Paragraph::insertInset(pos_type pos, Inset * inset,
1276                             Change const & change)
1277 {
1278         pimpl_->insertInset(pos, inset, change);
1279 }
1280
1281
1282 void Paragraph::insertInset(pos_type pos, Inset * inset,
1283                             Font const & font, Change const & change)
1284 {
1285         pimpl_->insertInset(pos, inset, change);
1286         // Set the font/language of the inset...
1287         setFont(pos, font);
1288 }
1289
1290
1291 bool Paragraph::insetAllowed(Inset_code code)
1292 {
1293         return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1294 }
1295
1296
1297 // Gets uninstantiated font setting at position.
1298 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1299                                          pos_type pos) const
1300 {
1301         if (pos > size()) {
1302                 lyxerr << " pos: " << pos << " size: " << size() << endl;
1303                 BOOST_ASSERT(pos <= size());
1304         }
1305
1306         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1307         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1308         for (; cit != end; ++cit)
1309                 if (cit->pos() >= pos)
1310                         break;
1311
1312         if (cit != end)
1313                 return cit->font();
1314
1315         if (pos == size() && !empty())
1316                 return getFontSettings(bparams, pos - 1);
1317
1318         return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1319 }
1320
1321
1322 FontSpan Paragraph::fontSpan(pos_type pos) const
1323 {
1324         BOOST_ASSERT(pos <= size());
1325         pos_type start = 0;
1326
1327         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1328         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1329         for (; cit != end; ++cit) {
1330                 if (cit->pos() >= pos) {
1331                         if (pos >= beginOfBody())
1332                                 return FontSpan(std::max(start, beginOfBody()),
1333                                                 cit->pos());
1334                         else
1335                                 return FontSpan(start,
1336                                                 std::min(beginOfBody() - 1,
1337                                                          cit->pos()));
1338                 }
1339                 start = cit->pos() + 1;
1340         }
1341
1342         // This should not happen, but if so, we take no chances.
1343         //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1344         //      << endl;
1345         return FontSpan(pos, pos);
1346 }
1347
1348
1349 // Gets uninstantiated font setting at position 0
1350 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1351 {
1352         if (!empty() && !pimpl_->fontlist.empty())
1353                 return pimpl_->fontlist[0].font();
1354
1355         return Font(Font::ALL_INHERIT, bparams.language);
1356 }
1357
1358
1359 // Gets the fully instantiated font at a given position in a paragraph
1360 // This is basically the same function as Text::GetFont() in text2.cpp.
1361 // The difference is that this one is used for generating the LaTeX file,
1362 // and thus cosmetic "improvements" are disallowed: This has to deliver
1363 // the true picture of the buffer. (Asger)
1364 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1365                                  Font const & outerfont) const
1366 {
1367         BOOST_ASSERT(pos >= 0);
1368
1369         Font font = getFontSettings(bparams, pos);
1370
1371         pos_type const body_pos = beginOfBody();
1372         if (pos < body_pos)
1373                 font.realize(layout_->labelfont);
1374         else
1375                 font.realize(layout_->font);
1376
1377         font.realize(outerfont);
1378         font.realize(bparams.getFont());
1379
1380         return font;
1381 }
1382
1383
1384 Font const Paragraph::getLabelFont
1385         (BufferParams const & bparams, Font const & outerfont) const
1386 {
1387         Font tmpfont = layout()->labelfont;
1388         tmpfont.setLanguage(getParLanguage(bparams));
1389         tmpfont.realize(outerfont);
1390         tmpfont.realize(bparams.getFont());
1391         return tmpfont;
1392 }
1393
1394
1395 Font const Paragraph::getLayoutFont
1396         (BufferParams const & bparams, Font const & outerfont) const
1397 {
1398         Font tmpfont = layout()->font;
1399         tmpfont.setLanguage(getParLanguage(bparams));
1400         tmpfont.realize(outerfont);
1401         tmpfont.realize(bparams.getFont());
1402         return tmpfont;
1403 }
1404
1405
1406 /// Returns the height of the highest font in range
1407 Font_size Paragraph::highestFontInRange
1408         (pos_type startpos, pos_type endpos, Font_size def_size) const
1409 {
1410         if (pimpl_->fontlist.empty())
1411                 return def_size;
1412
1413         Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1414         Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1415         for (; end_it != end; ++end_it) {
1416                 if (end_it->pos() >= endpos)
1417                         break;
1418         }
1419
1420         if (end_it != end)
1421                 ++end_it;
1422
1423         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1424         for (; cit != end; ++cit) {
1425                 if (cit->pos() >= startpos)
1426                         break;
1427         }
1428
1429         Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1430         for (; cit != end_it; ++cit) {
1431                 Font::FONT_SIZE size = cit->font().size();
1432                 if (size == Font::INHERIT_SIZE)
1433                         size = def_size;
1434                 if (size > maxsize && size <= Font::SIZE_HUGER)
1435                         maxsize = size;
1436         }
1437         return maxsize;
1438 }
1439
1440
1441 Paragraph::value_type
1442 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1443 {
1444         value_type c = getChar(pos);
1445         if (!lyxrc.rtl_support)
1446                 return c;
1447
1448         value_type uc = c;
1449         switch (c) {
1450         case '(':
1451                 uc = ')';
1452                 break;
1453         case ')':
1454                 uc = '(';
1455                 break;
1456         case '[':
1457                 uc = ']';
1458                 break;
1459         case ']':
1460                 uc = '[';
1461                 break;
1462         case '{':
1463                 uc = '}';
1464                 break;
1465         case '}':
1466                 uc = '{';
1467                 break;
1468         case '<':
1469                 uc = '>';
1470                 break;
1471         case '>':
1472                 uc = '<';
1473                 break;
1474         }
1475         if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1476                 return uc;
1477         else
1478                 return c;
1479 }
1480
1481
1482 void Paragraph::setFont(pos_type pos, Font const & font)
1483 {
1484         BOOST_ASSERT(pos <= size());
1485
1486         // First, reduce font against layout/label font
1487         // Update: The setCharFont() routine in text2.cpp already
1488         // reduces font, so we don't need to do that here. (Asger)
1489         // No need to simplify this because it will disappear
1490         // in a new kernel. (Asger)
1491         // Next search font table
1492
1493         Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1494         Pimpl::FontList::iterator it = beg;
1495         Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1496         for (; it != endit; ++it) {
1497                 if (it->pos() >= pos)
1498                         break;
1499         }
1500         size_t const i = distance(beg, it);
1501         bool notfound = (it == endit);
1502
1503         if (!notfound && pimpl_->fontlist[i].font() == font)
1504                 return;
1505
1506         bool begin = pos == 0 || notfound ||
1507                 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1508         // Is position pos is a beginning of a font block?
1509         bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1510         // Is position pos is the end of a font block?
1511         if (begin && end) { // A single char block
1512                 if (i + 1 < pimpl_->fontlist.size() &&
1513                     pimpl_->fontlist[i + 1].font() == font) {
1514                         // Merge the singleton block with the next block
1515                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1516                         if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1517                                 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1518                 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1519                         // Merge the singleton block with the previous block
1520                         pimpl_->fontlist[i - 1].pos(pos);
1521                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1522                 } else
1523                         pimpl_->fontlist[i].font(font);
1524         } else if (begin) {
1525                 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1526                         pimpl_->fontlist[i - 1].pos(pos);
1527                 else
1528                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1529                                         Pimpl::FontTable(pos, font));
1530         } else if (end) {
1531                 pimpl_->fontlist[i].pos(pos - 1);
1532                 if (!(i + 1 < pimpl_->fontlist.size() &&
1533                       pimpl_->fontlist[i + 1].font() == font))
1534                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1535                                         Pimpl::FontTable(pos, font));
1536         } else { // The general case. The block is splitted into 3 blocks
1537                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1538                                 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1539                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1540                                 Pimpl::FontTable(pos, font));
1541         }
1542 }
1543
1544
1545 void Paragraph::makeSameLayout(Paragraph const & par)
1546 {
1547         layout(par.layout());
1548         // move to pimpl?
1549         params() = par.params();
1550 }
1551
1552
1553 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1554 {
1555         if (isFreeSpacing())
1556                 return false;
1557
1558         int pos = 0;
1559         int count = 0;
1560
1561         while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1562                 if (eraseChar(pos, trackChanges))
1563                         ++count;
1564                 else
1565                         ++pos;
1566         }
1567
1568         return count > 0 || pos > 0;
1569 }
1570
1571
1572 bool Paragraph::hasSameLayout(Paragraph const & par) const
1573 {
1574         return par.layout() == layout() && params().sameLayout(par.params());
1575 }
1576
1577
1578 depth_type Paragraph::getDepth() const
1579 {
1580         return params().depth();
1581 }
1582
1583
1584 depth_type Paragraph::getMaxDepthAfter() const
1585 {
1586         if (layout()->isEnvironment())
1587                 return params().depth() + 1;
1588         else
1589                 return params().depth();
1590 }
1591
1592
1593 char Paragraph::getAlign() const
1594 {
1595         if (params().align() == LYX_ALIGN_LAYOUT)
1596                 return layout()->align;
1597         else
1598                 return params().align();
1599 }
1600
1601
1602 docstring const & Paragraph::getLabelstring() const
1603 {
1604         return params().labelString();
1605 }
1606
1607
1608 // the next two functions are for the manual labels
1609 docstring const Paragraph::getLabelWidthString() const
1610 {
1611         if (layout()->margintype == MARGIN_MANUAL)
1612                 return params().labelWidthString();
1613         else
1614                 return _("Senseless with this layout!");
1615 }
1616
1617
1618 void Paragraph::setLabelWidthString(docstring const & s)
1619 {
1620         params().labelWidthString(s);
1621 }
1622
1623
1624 docstring const Paragraph::translateIfPossible(docstring const & s,
1625                 BufferParams const & bparams) const
1626 {
1627         if (!support::isAscii(s) || s.empty()) {
1628                 // This must be a user defined layout. We cannot translate
1629                 // this, since gettext accepts only ascii keys.
1630                 return s;
1631         }
1632         // Probably standard layout, try to translate
1633         Messages & m = getMessages(getParLanguage(bparams)->code());
1634         return m.get(to_ascii(s));
1635 }
1636
1637
1638 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1639                 BufferParams const & bparams, bool process_appendix) const
1640 {
1641         TextClass const & tclass = bparams.getTextClass();
1642
1643         docstring fmt;
1644         if (process_appendix && params().appendix())
1645                 fmt = translateIfPossible(layout->labelstring_appendix(),
1646                         bparams);
1647         else
1648                 fmt = translateIfPossible(layout->labelstring(), bparams);
1649
1650         if (fmt.empty() && layout->labeltype == LABEL_COUNTER 
1651             && !layout->counter.empty())
1652                 fmt = "\\the" + layout->counter;
1653
1654         // handle 'inherited level parts' in 'fmt',
1655         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
1656         size_t const i = fmt.find('@', 0);
1657         if (i != docstring::npos) {
1658                 size_t const j = fmt.find('@', i + 1);
1659                 if (j != docstring::npos) {
1660                         docstring parent(fmt, i + 1, j - i - 1);
1661                         docstring label = expandLabel(tclass[parent], bparams,
1662                                                       process_appendix);
1663                         fmt = docstring(fmt, 0, i) + label 
1664                                 + docstring(fmt, j + 1, docstring::npos);
1665                 }
1666         }
1667
1668         return tclass.counters().counterLabel(fmt);
1669 }
1670
1671
1672 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1673 {
1674         layout(new_layout);
1675         LyXAlignment const oldAlign = params().align();
1676         
1677         if (!(oldAlign & layout()->alignpossible)) {
1678                 frontend::Alert::warning(_("Alignment not permitted"), 
1679                         _("The new layout does not permit the alignment previously used.\nSetting to default."));
1680                 params().align(LYX_ALIGN_LAYOUT);
1681         }
1682 }
1683
1684
1685 pos_type Paragraph::beginOfBody() const
1686 {
1687         return begin_of_body_;
1688 }
1689
1690
1691 void Paragraph::setBeginOfBody()
1692 {
1693         if (layout()->labeltype != LABEL_MANUAL) {
1694                 begin_of_body_ = 0;
1695                 return;
1696         }
1697
1698         // Unroll the first two cycles of the loop
1699         // and remember the previous character to
1700         // remove unnecessary getChar() calls
1701         pos_type i = 0;
1702         pos_type end = size();
1703         if (i < end && !isNewline(i)) {
1704                 ++i;
1705                 char_type previous_char = 0;
1706                 char_type temp = 0;
1707                 if (i < end) {
1708                         previous_char = text_[i];
1709                         if (!isNewline(i)) {
1710                                 ++i;
1711                                 while (i < end && previous_char != ' ') {
1712                                         temp = text_[i];
1713                                         if (isNewline(i))
1714                                                 break;
1715                                         ++i;
1716                                         previous_char = temp;
1717                                 }
1718                         }
1719                 }
1720         }
1721
1722         begin_of_body_ = i;
1723 }
1724
1725
1726 // returns -1 if inset not found
1727 int Paragraph::getPositionOfInset(Inset const * inset) const
1728 {
1729         // Find the entry.
1730         InsetList::const_iterator it = insetlist.begin();
1731         InsetList::const_iterator end = insetlist.end();
1732         for (; it != end; ++it)
1733                 if (it->inset == inset)
1734                         return it->pos;
1735         return -1;
1736 }
1737
1738
1739 InsetBibitem * Paragraph::bibitem() const
1740 {
1741         if (!insetlist.empty()) {
1742                 Inset * inset = insetlist.begin()->inset;
1743                 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1744                         return static_cast<InsetBibitem *>(inset);
1745         }
1746         return 0;
1747 }
1748
1749
1750 bool Paragraph::forceDefaultParagraphs() const
1751 {
1752         return inInset() && inInset()->forceDefaultParagraphs(0);
1753 }
1754
1755
1756 namespace {
1757
1758 // paragraphs inside floats need different alignment tags to avoid
1759 // unwanted space
1760
1761 bool noTrivlistCentering(Inset::Code code)
1762 {
1763         return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1764 }
1765
1766
1767 string correction(string const & orig)
1768 {
1769         if (orig == "flushleft")
1770                 return "raggedright";
1771         if (orig == "flushright")
1772                 return "raggedleft";
1773         if (orig == "center")
1774                 return "centering";
1775         return orig;
1776 }
1777
1778
1779 string const corrected_env(string const & suffix, string const & env,
1780         Inset::Code code)
1781 {
1782         string output = suffix + "{";
1783         if (noTrivlistCentering(code))
1784                 output += correction(env);
1785         else
1786                 output += env;
1787         output += "}";
1788         if (suffix == "\\begin")
1789                 output += "\n";
1790         return output;
1791 }
1792
1793
1794 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1795 {
1796         if (!contains(str, "\n"))
1797                 column += str.size();
1798         else {
1799                 string tmp;
1800                 texrow.newline();
1801                 column = rsplit(str, tmp, '\n').size();
1802         }
1803 }
1804
1805 } // namespace anon
1806
1807
1808 // This could go to ParagraphParameters if we want to
1809 int Paragraph::startTeXParParams(BufferParams const & bparams,
1810                                  odocstream & os, TexRow & texrow,
1811                                  bool moving_arg) const
1812 {
1813         int column = 0;
1814
1815         if (params().noindent()) {
1816                 os << "\\noindent ";
1817                 column += 10;
1818         }
1819         
1820         LyXAlignment const curAlign = params().align();
1821
1822         if (curAlign == layout()->align)
1823                 return column;
1824
1825         switch (curAlign) {
1826         case LYX_ALIGN_NONE:
1827         case LYX_ALIGN_BLOCK:
1828         case LYX_ALIGN_LAYOUT:
1829         case LYX_ALIGN_SPECIAL:
1830                 break;
1831         case LYX_ALIGN_LEFT:
1832         case LYX_ALIGN_RIGHT:
1833         case LYX_ALIGN_CENTER:
1834                 if (moving_arg) {
1835                         os << "\\protect";
1836                         column += 8;
1837                 }
1838                 break;
1839         }
1840
1841         switch (curAlign) {
1842         case LYX_ALIGN_NONE:
1843         case LYX_ALIGN_BLOCK:
1844         case LYX_ALIGN_LAYOUT:
1845         case LYX_ALIGN_SPECIAL:
1846                 break;
1847         case LYX_ALIGN_LEFT: {
1848                 string output;
1849                 if (getParLanguage(bparams)->babel() != "hebrew")
1850                         output = corrected_env("\\begin", "flushleft", ownerCode());
1851                 else
1852                         output = corrected_env("\\begin", "flushright", ownerCode());
1853                 os << from_ascii(output);
1854                 adjust_row_column(output, texrow, column);
1855                 break;
1856         } case LYX_ALIGN_RIGHT: {
1857                 string output;
1858                 if (getParLanguage(bparams)->babel() != "hebrew")
1859                         output = corrected_env("\\begin", "flushright", ownerCode());
1860                 else
1861                         output = corrected_env("\\begin", "flushleft", ownerCode());
1862                 os << from_ascii(output);
1863                 adjust_row_column(output, texrow, column);
1864                 break;
1865         } case LYX_ALIGN_CENTER: {
1866                 string output;
1867                 output = corrected_env("\\begin", "center", ownerCode());
1868                 os << from_ascii(output);
1869                 adjust_row_column(output, texrow, column);
1870                 break;
1871         }
1872         }
1873
1874         return column;
1875 }
1876
1877
1878 // This could go to ParagraphParameters if we want to
1879 int Paragraph::endTeXParParams(BufferParams const & bparams,
1880                                odocstream & os, TexRow & texrow,
1881                                bool moving_arg) const
1882 {
1883         int column = 0;
1884
1885         switch (params().align()) {
1886         case LYX_ALIGN_NONE:
1887         case LYX_ALIGN_BLOCK:
1888         case LYX_ALIGN_LAYOUT:
1889         case LYX_ALIGN_SPECIAL:
1890                 break;
1891         case LYX_ALIGN_LEFT:
1892         case LYX_ALIGN_RIGHT:
1893         case LYX_ALIGN_CENTER:
1894                 if (moving_arg) {
1895                         os << "\\protect";
1896                         column = 8;
1897                 }
1898                 break;
1899         }
1900
1901         switch (params().align()) {
1902         case LYX_ALIGN_NONE:
1903         case LYX_ALIGN_BLOCK:
1904         case LYX_ALIGN_LAYOUT:
1905         case LYX_ALIGN_SPECIAL:
1906                 break;
1907         case LYX_ALIGN_LEFT: {
1908                 string output;
1909                 if (getParLanguage(bparams)->babel() != "hebrew")
1910                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1911                 else
1912                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1913                 os << from_ascii(output);
1914                 adjust_row_column(output, texrow, column);
1915                 break;
1916         } case LYX_ALIGN_RIGHT: {
1917                 string output;
1918                 if (getParLanguage(bparams)->babel() != "hebrew")
1919                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1920                 else
1921                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1922                 os << from_ascii(output);
1923                 adjust_row_column(output, texrow, column);
1924                 break;
1925         } case LYX_ALIGN_CENTER: {
1926                 string output;
1927                 output = corrected_env("\n\\par\\end", "center", ownerCode());
1928                 os << from_ascii(output);
1929                 adjust_row_column(output, texrow, column);
1930                 break;
1931         }
1932         }
1933
1934         return column;
1935 }
1936
1937
1938 // This one spits out the text of the paragraph
1939 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1940                                 BufferParams const & bparams,
1941                                 Font const & outerfont,
1942                                 odocstream & os, TexRow & texrow,
1943                                 OutputParams const & runparams) const
1944 {
1945         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...     " << this << endl;
1946
1947         bool return_value = false;
1948
1949         LayoutPtr style;
1950
1951         // well we have to check if we are in an inset with unlimited
1952         // length (all in one row) if that is true then we don't allow
1953         // any special options in the paragraph and also we don't allow
1954         // any environment other than the default layout of the text class
1955         // to be valid!
1956         bool asdefault = forceDefaultParagraphs();
1957
1958         if (asdefault) {
1959                 style = bparams.getTextClass().defaultLayout();
1960         } else {
1961                 style = layout();
1962         }
1963
1964         // Current base font for all inherited font changes, without any
1965         // change caused by an individual character, except for the language:
1966         // It is set to the language of the first character.
1967         // As long as we are in the label, this font is the base font of the
1968         // label. Before the first body character it is set to the base font
1969         // of the body.
1970         Font basefont;
1971
1972         // Maybe we have to create a optional argument.
1973         pos_type body_pos = beginOfBody();
1974         unsigned int column = 0;
1975
1976         if (body_pos > 0) {
1977                 // the optional argument is kept in curly brackets in
1978                 // case it contains a ']'
1979                 os << "[{";
1980                 column += 2;
1981                 basefont = getLabelFont(bparams, outerfont);
1982         } else {
1983                 basefont = getLayoutFont(bparams, outerfont);
1984         }
1985
1986         // Which font is currently active?
1987         Font running_font(basefont);
1988         // Do we have an open font change?
1989         bool open_font = false;
1990
1991         Change runningChange = Change(Change::UNCHANGED);
1992
1993         texrow.start(id(), 0);
1994
1995         // if the paragraph is empty, the loop will not be entered at all
1996         if (empty()) {
1997                 if (style->isCommand()) {
1998                         os << '{';
1999                         ++column;
2000                 }
2001                 if (!asdefault)
2002                         column += startTeXParParams(bparams, os, texrow,
2003                                                     runparams.moving_arg);
2004         }
2005
2006         for (pos_type i = 0; i < size(); ++i) {
2007                 // First char in paragraph or after label?
2008                 if (i == body_pos) {
2009                         if (body_pos > 0) {
2010                                 if (open_font) {
2011                                         column += running_font.latexWriteEndChanges(
2012                                                 os, bparams, runparams,
2013                                                 basefont, basefont);
2014                                         open_font = false;
2015                                 }
2016                                 basefont = getLayoutFont(bparams, outerfont);
2017                                 running_font = basefont;
2018
2019                                 column += Changes::latexMarkChange(os, bparams,
2020                                                 runningChange, Change(Change::UNCHANGED));
2021                                 runningChange = Change(Change::UNCHANGED);
2022
2023                                 os << "}] ";
2024                                 column +=3;
2025                         }
2026                         if (style->isCommand()) {
2027                                 os << '{';
2028                                 ++column;
2029                         }
2030
2031                         if (!asdefault)
2032                                 column += startTeXParParams(bparams, os,
2033                                                             texrow,
2034                                                             runparams.moving_arg);
2035                 }
2036
2037                 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2038                                                                  : pimpl_->lookupChange(i);
2039
2040                 if (bparams.outputChanges && runningChange != change) {
2041                         if (open_font) {
2042                                 column += running_font.latexWriteEndChanges(
2043                                                 os, bparams, runparams, basefont, basefont);
2044                                 open_font = false;
2045                         }
2046                         basefont = getLayoutFont(bparams, outerfont);
2047                         running_font = basefont;
2048
2049                         column += Changes::latexMarkChange(os, bparams, runningChange, change);
2050                         runningChange = change;
2051                 }
2052
2053                 // do not output text which is marked deleted
2054                 // if change tracking output is disabled
2055                 if (!bparams.outputChanges && change.type == Change::DELETED) {
2056                         continue;
2057                 }
2058
2059                 ++column;
2060
2061                 value_type const c = getChar(i);
2062
2063                 // Fully instantiated font
2064                 Font const font = getFont(bparams, i, outerfont);
2065
2066                 Font const last_font = running_font;
2067
2068                 // Do we need to close the previous font?
2069                 if (open_font &&
2070                     (font != running_font ||
2071                      font.language() != running_font.language()))
2072                 {
2073                         column += running_font.latexWriteEndChanges(
2074                                         os, bparams, runparams, basefont,
2075                                         (i == body_pos-1) ? basefont : font);
2076                         running_font = basefont;
2077                         open_font = false;
2078                 }
2079
2080                 // Switch file encoding if necessary
2081                 if (runparams.encoding->package() == Encoding::inputenc &&
2082                     font.language()->encoding()->package() == Encoding::inputenc) {
2083                         std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2084                                         runparams.moving_arg, *(runparams.encoding),
2085                                         *(font.language()->encoding()));
2086                         if (enc_switch.first) {
2087                                 column += enc_switch.second;
2088                                 runparams.encoding = font.language()->encoding();
2089                         }
2090                 }
2091
2092                 // Do we need to change font?
2093                 if ((font != running_font ||
2094                      font.language() != running_font.language()) &&
2095                         i != body_pos - 1)
2096                 {
2097                         odocstringstream ods;
2098                         column += font.latexWriteStartChanges(ods, bparams,
2099                                                               runparams, basefont,
2100                                                               last_font);
2101                         running_font = font;
2102                         open_font = true;
2103                         docstring fontchange = ods.str();
2104                         // check if the fontchange ends with a trailing blank
2105                         // (like "\small " (see bug 3382)
2106                         if (suffixIs(fontchange, ' ') && c == ' ')
2107                                 os << fontchange.substr(0, fontchange.size() - 1) 
2108                                    << from_ascii("{}");
2109                         else
2110                                 os << fontchange;
2111                 }
2112
2113                 if (c == ' ') {
2114                         // Do not print the separation of the optional argument
2115                         // if style->pass_thru is false. This works because
2116                         // simpleTeXSpecialChars ignores spaces if
2117                         // style->pass_thru is false.
2118                         if (i != body_pos - 1) {
2119                                 if (pimpl_->simpleTeXBlanks(
2120                                                 *(runparams.encoding), os, texrow,
2121                                                 i, column, font, *style))
2122                                         // A surrogate pair was output. We
2123                                         // must not call simpleTeXSpecialChars
2124                                         // in this iteration, since
2125                                         // simpleTeXBlanks incremented i, and
2126                                         // simpleTeXSpecialChars would output
2127                                         // the combining character again.
2128                                         continue;
2129                         }
2130                 }
2131
2132                 OutputParams rp = runparams;
2133                 rp.free_spacing = style->free_spacing;
2134                 rp.local_font = &font;
2135                 rp.intitle = style->intitle;
2136                 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2137                                         texrow, rp, running_font,
2138                                         basefont, outerfont, open_font,
2139                                         runningChange, *style, i, column, c);
2140
2141                 // Set the encoding to that returned from simpleTeXSpecialChars (see
2142                 // comment for encoding member in OutputParams.h)
2143                 runparams.encoding = rp.encoding;
2144         }
2145
2146         // If we have an open font definition, we have to close it
2147         if (open_font) {
2148 #ifdef FIXED_LANGUAGE_END_DETECTION
2149                 if (next_) {
2150                         running_font
2151                                 .latexWriteEndChanges(os, bparams, runparams,
2152                                         basefont,
2153                                         next_->getFont(bparams, 0, outerfont));
2154                 } else {
2155                         running_font.latexWriteEndChanges(os, bparams,
2156                                         runparams, basefont, basefont);
2157                 }
2158 #else
2159 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2160 //FIXME: there as we start another \selectlanguage with the next paragraph if
2161 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2162                 running_font.latexWriteEndChanges(os, bparams, runparams,
2163                                 basefont, basefont);
2164 #endif
2165         }
2166
2167         column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2168
2169         // Needed if there is an optional argument but no contents.
2170         if (body_pos > 0 && body_pos == size()) {
2171                 os << "}]~";
2172                 return_value = false;
2173         }
2174
2175         if (!asdefault) {
2176                 column += endTeXParParams(bparams, os, texrow,
2177                                           runparams.moving_arg);
2178         }
2179
2180         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2181         return return_value;
2182 }
2183
2184
2185 namespace {
2186
2187 enum PAR_TAG {
2188         PAR_NONE=0,
2189         TT = 1,
2190         SF = 2,
2191         BF = 4,
2192         IT = 8,
2193         SL = 16,
2194         EM = 32
2195 };
2196
2197
2198 string tag_name(PAR_TAG const & pt) {
2199         switch (pt) {
2200         case PAR_NONE: return "!-- --";
2201         case TT: return "tt";
2202         case SF: return "sf";
2203         case BF: return "bf";
2204         case IT: return "it";
2205         case SL: return "sl";
2206         case EM: return "em";
2207         }
2208         return "";
2209 }
2210
2211
2212 inline
2213 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2214 {
2215         p1 = static_cast<PAR_TAG>(p1 | p2);
2216 }
2217
2218
2219 inline
2220 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2221 {
2222         p1 = static_cast<PAR_TAG>(p1 & ~p2);
2223 }
2224
2225 } // anon
2226
2227
2228 bool Paragraph::emptyTag() const
2229 {
2230         for (pos_type i = 0; i < size(); ++i) {
2231                 if (isInset(i)) {
2232                         Inset const * inset = getInset(i);
2233                         Inset::Code lyx_code = inset->lyxCode();
2234                         if (lyx_code != Inset::TOC_CODE &&
2235                             lyx_code != Inset::INCLUDE_CODE &&
2236                             lyx_code != Inset::GRAPHICS_CODE &&
2237                             lyx_code != Inset::ERT_CODE &&
2238                             lyx_code != Inset::LISTINGS_CODE &&
2239                             lyx_code != Inset::FLOAT_CODE &&
2240                             lyx_code != Inset::TABULAR_CODE) {
2241                                 return false;
2242                         }
2243                 } else {
2244                         value_type c = getChar(i);
2245                         if (c != ' ' && c != '\t')
2246                                 return false;
2247                 }
2248         }
2249         return true;
2250 }
2251
2252
2253 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2254 {
2255         for (pos_type i = 0; i < size(); ++i) {
2256                 if (isInset(i)) {
2257                         Inset const * inset = getInset(i);
2258                         Inset::Code lyx_code = inset->lyxCode();
2259                         if (lyx_code == Inset::LABEL_CODE) {
2260                                 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2261                                 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2262                         }
2263                 }
2264
2265         }
2266         return string();
2267 }
2268
2269
2270 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2271 {
2272         pos_type i;
2273         for (i = 0; i < size(); ++i) {
2274                 if (isInset(i)) {
2275                         Inset const * inset = getInset(i);
2276                         inset->docbook(buf, os, runparams);
2277                 } else {
2278                         value_type c = getChar(i);
2279                         if (c == ' ')
2280                                 break;
2281                         os << sgml::escapeChar(c);
2282                 }
2283         }
2284         return i;
2285 }
2286
2287
2288 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2289 {
2290         Font font_old;
2291
2292         for (pos_type i = initial; i < size(); ++i) {
2293                 Font font = getFont(buf.params(), i, outerfont);
2294                 if (isInset(i))
2295                         return false;
2296                 if (i != initial && font != font_old)
2297                         return false;
2298                 font_old = font;
2299         }
2300
2301         return true;
2302 }
2303
2304
2305 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2306                                     odocstream & os,
2307                                     OutputParams const & runparams,
2308                                     Font const & outerfont,
2309                                     pos_type initial) const
2310 {
2311         bool emph_flag = false;
2312
2313         LayoutPtr const & style = layout();
2314         Font font_old =
2315                 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2316
2317         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2318                 os << "]]>";
2319
2320         // parsing main loop
2321         for (pos_type i = initial; i < size(); ++i) {
2322                 Font font = getFont(buf.params(), i, outerfont);
2323
2324                 // handle <emphasis> tag
2325                 if (font_old.emph() != font.emph()) {
2326                         if (font.emph() == Font::ON) {
2327                                 os << "<emphasis>";
2328                                 emph_flag = true;
2329                         } else if (i != initial) {
2330                                 os << "</emphasis>";
2331                                 emph_flag = false;
2332                         }
2333                 }
2334
2335                 if (isInset(i)) {
2336                         Inset const * inset = getInset(i);
2337                         inset->docbook(buf, os, runparams);
2338                 } else {
2339                         value_type c = getChar(i);
2340
2341                         if (style->pass_thru)
2342                                 os.put(c);
2343                         else
2344                                 os << sgml::escapeChar(c);
2345                 }
2346                 font_old = font;
2347         }
2348
2349         if (emph_flag) {
2350                 os << "</emphasis>";
2351         }
2352
2353         if (style->free_spacing)
2354                 os << '\n';
2355         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2356                 os << "<![CDATA[";
2357 }
2358
2359
2360 bool Paragraph::isNewline(pos_type pos) const
2361 {
2362         return isInset(pos)
2363                 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2364 }
2365
2366
2367 bool Paragraph::isLineSeparator(pos_type pos) const
2368 {
2369         value_type const c = getChar(pos);
2370         return isLineSeparatorChar(c)
2371                 || (c == Paragraph::META_INSET && getInset(pos) &&
2372                 getInset(pos)->isLineSeparator());
2373 }
2374
2375
2376 /// Used by the spellchecker
2377 bool Paragraph::isLetter(pos_type pos) const
2378 {
2379         if (isInset(pos))
2380                 return getInset(pos)->isLetter();
2381         else {
2382                 value_type const c = getChar(pos);
2383                 return isLetterChar(c) || isDigit(c);
2384         }
2385 }
2386
2387
2388 Language const *
2389 Paragraph::getParLanguage(BufferParams const & bparams) const
2390 {
2391         if (!empty())
2392                 return getFirstFontSettings(bparams).language();
2393         // FIXME: we should check the prev par as well (Lgb)
2394         return bparams.language;
2395 }
2396
2397
2398 bool Paragraph::isRTL(BufferParams const & bparams) const
2399 {
2400         return lyxrc.rtl_support
2401                 && getParLanguage(bparams)->rightToLeft()
2402                 && ownerCode() != Inset::ERT_CODE
2403                 && ownerCode() != Inset::LISTINGS_CODE;
2404 }
2405
2406
2407 void Paragraph::changeLanguage(BufferParams const & bparams,
2408                                Language const * from, Language const * to)
2409 {
2410         // change language including dummy font change at the end
2411         for (pos_type i = 0; i <= size(); ++i) {
2412                 Font font = getFontSettings(bparams, i);
2413                 if (font.language() == from) {
2414                         font.setLanguage(to);
2415                         setFont(i, font);
2416                 }
2417         }
2418 }
2419
2420
2421 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2422 {
2423         Language const * doc_language = bparams.language;
2424         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2425         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2426
2427         for (; cit != end; ++cit)
2428                 if (cit->font().language() != ignore_language &&
2429                     cit->font().language() != latex_language &&
2430                     cit->font().language() != doc_language)
2431                         return true;
2432         return false;
2433 }
2434
2435
2436 // Convert the paragraph to a string.
2437 // Used for building the table of contents
2438 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2439 {
2440         return asString(buffer, 0, size(), label);
2441 }
2442
2443
2444 docstring const Paragraph::asString(Buffer const & buffer,
2445                                  pos_type beg, pos_type end, bool label) const
2446 {
2447
2448         odocstringstream os;
2449
2450         if (beg == 0 && label && !params().labelString().empty())
2451                 os << params().labelString() << ' ';
2452
2453         for (pos_type i = beg; i < end; ++i) {
2454                 value_type const c = getChar(i);
2455                 if (isPrintable(c))
2456                         os.put(c);
2457                 else if (c == META_INSET)
2458                         getInset(i)->textString(buffer, os);
2459         }
2460
2461         return os.str();
2462 }
2463
2464
2465 void Paragraph::setInsetOwner(Inset * inset)
2466 {
2467         pimpl_->inset_owner = inset;
2468 }
2469
2470
2471 Change const & Paragraph::lookupChange(pos_type pos) const
2472 {
2473         BOOST_ASSERT(pos <= size());
2474         return pimpl_->lookupChange(pos);
2475 }
2476
2477
2478 bool Paragraph::isChanged(pos_type start, pos_type end) const
2479 {
2480         return pimpl_->isChanged(start, end);
2481 }
2482
2483
2484 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2485 {
2486         return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2487 }
2488
2489
2490 void Paragraph::setChange(Change const & change)
2491 {
2492         pimpl_->setChange(change);
2493 }
2494
2495
2496 void Paragraph::setChange(pos_type pos, Change const & change)
2497 {
2498         pimpl_->setChange(pos, change);
2499 }
2500
2501
2502 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2503 {
2504         return pimpl_->acceptChanges(bparams, start, end);
2505 }
2506
2507
2508 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2509 {
2510         return pimpl_->rejectChanges(bparams, start, end);
2511 }
2512
2513
2514 int Paragraph::id() const
2515 {
2516         return pimpl_->id_;
2517 }
2518
2519
2520 LayoutPtr const & Paragraph::layout() const
2521 {
2522         return layout_;
2523 }
2524
2525
2526 void Paragraph::layout(LayoutPtr const & new_layout)
2527 {
2528         layout_ = new_layout;
2529 }
2530
2531
2532 Inset * Paragraph::inInset() const
2533 {
2534         return pimpl_->inset_owner;
2535 }
2536
2537
2538 Inset::Code Paragraph::ownerCode() const
2539 {
2540         return pimpl_->inset_owner
2541                 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2542 }
2543
2544
2545 ParagraphParameters & Paragraph::params()
2546 {
2547         return pimpl_->params;
2548 }
2549
2550
2551 ParagraphParameters const & Paragraph::params() const
2552 {
2553         return pimpl_->params;
2554 }
2555
2556
2557 bool Paragraph::isFreeSpacing() const
2558 {
2559         if (layout()->free_spacing)
2560                 return true;
2561
2562         // for now we just need this, later should we need this in some
2563         // other way we can always add a function to Inset too.
2564         return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2565 }
2566
2567
2568 bool Paragraph::allowEmpty() const
2569 {
2570         if (layout()->keepempty)
2571                 return true;
2572         return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2573 }
2574
2575
2576 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2577 {
2578         if (!Encodings::is_arabic(c))
2579                 return c;
2580
2581         value_type prev_char = ' ';
2582         value_type next_char = ' ';
2583
2584         for (pos_type i = pos - 1; i >= 0; --i) {
2585                 value_type const par_char = getChar(i);
2586                 if (!Encodings::isComposeChar_arabic(par_char)) {
2587                         prev_char = par_char;
2588                         break;
2589                 }
2590         }
2591
2592         for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2593                 value_type const par_char = getChar(i);
2594                 if (!Encodings::isComposeChar_arabic(par_char)) {
2595                         next_char = par_char;
2596                         break;
2597                 }
2598         }
2599
2600         if (Encodings::is_arabic(next_char)) {
2601                 if (Encodings::is_arabic(prev_char) &&
2602                         !Encodings::is_arabic_special(prev_char))
2603                         return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2604                 else
2605                         return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2606         } else {
2607                 if (Encodings::is_arabic(prev_char) &&
2608                         !Encodings::is_arabic_special(prev_char))
2609                         return Encodings::transformChar(c, Encodings::FORM_FINAL);
2610                 else
2611                         return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2612         }
2613 }
2614
2615
2616 int Paragraph::checkBiblio(bool track_changes)
2617 {
2618         //FIXME From JS:
2619         //This is getting more and more a mess. ...We really should clean
2620         //up this bibitem issue for 1.6. See also bug 2743.
2621
2622         // Add bibitem insets if necessary
2623         if (layout()->labeltype != LABEL_BIBLIO)
2624                 return 0;
2625
2626         bool hasbibitem = !insetlist.empty()
2627                 // Insist on it being in pos 0
2628                 && getChar(0) == Paragraph::META_INSET
2629                 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2630
2631         docstring oldkey;
2632         docstring oldlabel;
2633
2634         // remove a bibitem in pos != 0
2635         // restore it later in pos 0 if necessary
2636         // (e.g. if a user inserts contents _before_ the item)
2637         // we're assuming there's only one of these, which there
2638         // should be.
2639         int erasedInsetPosition = -1;
2640         InsetList::iterator it = insetlist.begin();
2641         InsetList::iterator end = insetlist.end();
2642         for (; it != end; ++it)
2643                 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2644                     && it->pos > 0) {
2645                         InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2646                         oldkey = olditem->getParam("key");
2647                         oldlabel = olditem->getParam("label");
2648                         erasedInsetPosition = it->pos;
2649                         eraseChar(erasedInsetPosition, track_changes);
2650                         break;
2651         }
2652
2653         //There was an InsetBibitem at the beginning, and we didn't
2654         //have to erase one.
2655         if (hasbibitem && erasedInsetPosition < 0)
2656                         return 0;
2657
2658         //There was an InsetBibitem at the beginning and we did have to
2659         //erase one. So we give its properties to the beginning inset.
2660         if (hasbibitem) {
2661                 InsetBibitem * inset =
2662                         static_cast<InsetBibitem *>(insetlist.begin()->inset);
2663                 if (!oldkey.empty())
2664                         inset->setParam("key", oldkey);
2665                 inset->setParam("label", oldlabel);
2666                 return -erasedInsetPosition;
2667         }
2668
2669         //There was no inset at the beginning, so we need to create one with
2670         //the key and label of the one we erased.
2671         InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2672         // restore values of previously deleted item in this par.
2673         if (!oldkey.empty())
2674                 inset->setParam("key", oldkey);
2675         inset->setParam("label", oldlabel);
2676         insertInset(0, static_cast<Inset *>(inset),
2677                     Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2678
2679         return 1;
2680 }
2681
2682
2683 void Paragraph::checkAuthors(AuthorList const & authorList)
2684 {
2685         pimpl_->changes_.checkAuthors(authorList);
2686 }
2687
2688 } // namespace lyx