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