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