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