]> git.lyx.org Git - lyx.git/blob - src/Paragraph.cpp
* Only enter inset which return true on isActive(). This is the behavior in the curso...
[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         switch (params().align()) {
1786         case LYX_ALIGN_NONE:
1787         case LYX_ALIGN_BLOCK:
1788         case LYX_ALIGN_LAYOUT:
1789         case LYX_ALIGN_SPECIAL:
1790                 break;
1791         case LYX_ALIGN_LEFT:
1792         case LYX_ALIGN_RIGHT:
1793         case LYX_ALIGN_CENTER:
1794                 if (moving_arg) {
1795                         os << "\\protect";
1796                         column += 8;
1797                 }
1798                 break;
1799         }
1800
1801         switch (params().align()) {
1802         case LYX_ALIGN_NONE:
1803         case LYX_ALIGN_BLOCK:
1804         case LYX_ALIGN_LAYOUT:
1805         case LYX_ALIGN_SPECIAL:
1806                 break;
1807         case LYX_ALIGN_LEFT: {
1808                 string output;
1809                 if (getParLanguage(bparams)->babel() != "hebrew")
1810                         output = corrected_env("\\begin", "flushleft", ownerCode());
1811                 else
1812                         output = corrected_env("\\begin", "flushright", ownerCode());
1813                 os << from_ascii(output);
1814                 adjust_row_column(output, texrow, column);
1815                 break;
1816         } case LYX_ALIGN_RIGHT: {
1817                 string output;
1818                 if (getParLanguage(bparams)->babel() != "hebrew")
1819                         output = corrected_env("\\begin", "flushright", ownerCode());
1820                 else
1821                         output = corrected_env("\\begin", "flushleft", ownerCode());
1822                 os << from_ascii(output);
1823                 adjust_row_column(output, texrow, column);
1824                 break;
1825         } case LYX_ALIGN_CENTER: {
1826                 string output;
1827                 output = corrected_env("\\begin", "center", ownerCode());
1828                 os << from_ascii(output);
1829                 adjust_row_column(output, texrow, column);
1830                 break;
1831         }
1832         }
1833
1834         return column;
1835 }
1836
1837
1838 // This could go to ParagraphParameters if we want to
1839 int Paragraph::endTeXParParams(BufferParams const & bparams,
1840                                odocstream & os, TexRow & texrow,
1841                                bool moving_arg) const
1842 {
1843         int column = 0;
1844
1845         switch (params().align()) {
1846         case LYX_ALIGN_NONE:
1847         case LYX_ALIGN_BLOCK:
1848         case LYX_ALIGN_LAYOUT:
1849         case LYX_ALIGN_SPECIAL:
1850                 break;
1851         case LYX_ALIGN_LEFT:
1852         case LYX_ALIGN_RIGHT:
1853         case LYX_ALIGN_CENTER:
1854                 if (moving_arg) {
1855                         os << "\\protect";
1856                         column = 8;
1857                 }
1858                 break;
1859         }
1860
1861         switch (params().align()) {
1862         case LYX_ALIGN_NONE:
1863         case LYX_ALIGN_BLOCK:
1864         case LYX_ALIGN_LAYOUT:
1865         case LYX_ALIGN_SPECIAL:
1866                 break;
1867         case LYX_ALIGN_LEFT: {
1868                 string output;
1869                 if (getParLanguage(bparams)->babel() != "hebrew")
1870                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1871                 else
1872                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1873                 os << from_ascii(output);
1874                 adjust_row_column(output, texrow, column);
1875                 break;
1876         } case LYX_ALIGN_RIGHT: {
1877                 string output;
1878                 if (getParLanguage(bparams)->babel() != "hebrew")
1879                         output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1880                 else
1881                         output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1882                 os << from_ascii(output);
1883                 adjust_row_column(output, texrow, column);
1884                 break;
1885         } case LYX_ALIGN_CENTER: {
1886                 string output;
1887                 output = corrected_env("\n\\par\\end", "center", ownerCode());
1888                 os << from_ascii(output);
1889                 adjust_row_column(output, texrow, column);
1890                 break;
1891         }
1892         }
1893
1894         return column;
1895 }
1896
1897
1898 // This one spits out the text of the paragraph
1899 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1900                                 BufferParams const & bparams,
1901                                 Font const & outerfont,
1902                                 odocstream & os, TexRow & texrow,
1903                                 OutputParams const & runparams) const
1904 {
1905         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...     " << this << endl;
1906
1907         bool return_value = false;
1908
1909         Layout_ptr style;
1910
1911         // well we have to check if we are in an inset with unlimited
1912         // length (all in one row) if that is true then we don't allow
1913         // any special options in the paragraph and also we don't allow
1914         // any environment other than the default layout of the text class
1915         // to be valid!
1916         bool asdefault = forceDefaultParagraphs();
1917
1918         if (asdefault) {
1919                 style = bparams.getTextClass().defaultLayout();
1920         } else {
1921                 style = layout();
1922         }
1923
1924         // Current base font for all inherited font changes, without any
1925         // change caused by an individual character, except for the language:
1926         // It is set to the language of the first character.
1927         // As long as we are in the label, this font is the base font of the
1928         // label. Before the first body character it is set to the base font
1929         // of the body.
1930         Font basefont;
1931
1932         // Maybe we have to create a optional argument.
1933         pos_type body_pos = beginOfBody();
1934         unsigned int column = 0;
1935
1936         if (body_pos > 0) {
1937                 // the optional argument is kept in curly brackets in
1938                 // case it contains a ']'
1939                 os << "[{";
1940                 column += 2;
1941                 basefont = getLabelFont(bparams, outerfont);
1942         } else {
1943                 basefont = getLayoutFont(bparams, outerfont);
1944         }
1945
1946         // Which font is currently active?
1947         Font running_font(basefont);
1948         // Do we have an open font change?
1949         bool open_font = false;
1950
1951         Change runningChange = Change(Change::UNCHANGED);
1952
1953         texrow.start(id(), 0);
1954
1955         // if the paragraph is empty, the loop will not be entered at all
1956         if (empty()) {
1957                 if (style->isCommand()) {
1958                         os << '{';
1959                         ++column;
1960                 }
1961                 if (!asdefault)
1962                         column += startTeXParParams(bparams, os, texrow,
1963                                                     runparams.moving_arg);
1964         }
1965
1966         for (pos_type i = 0; i < size(); ++i) {
1967                 // First char in paragraph or after label?
1968                 if (i == body_pos) {
1969                         if (body_pos > 0) {
1970                                 if (open_font) {
1971                                         column += running_font.latexWriteEndChanges(
1972                                                 os, bparams, runparams,
1973                                                 basefont, basefont);
1974                                         open_font = false;
1975                                 }
1976                                 basefont = getLayoutFont(bparams, outerfont);
1977                                 running_font = basefont;
1978
1979                                 column += Changes::latexMarkChange(os, bparams,
1980                                                 runningChange, Change(Change::UNCHANGED));
1981                                 runningChange = Change(Change::UNCHANGED);
1982
1983                                 os << "}] ";
1984                                 column +=3;
1985                         }
1986                         if (style->isCommand()) {
1987                                 os << '{';
1988                                 ++column;
1989                         }
1990
1991                         if (!asdefault)
1992                                 column += startTeXParParams(bparams, os,
1993                                                             texrow,
1994                                                             runparams.moving_arg);
1995                 }
1996
1997                 Change const & change = pimpl_->lookupChange(i);
1998
1999                 if (bparams.outputChanges && runningChange != change) {
2000                         if (open_font) {
2001                                 column += running_font.latexWriteEndChanges(
2002                                                 os, bparams, runparams, basefont, basefont);
2003                                 open_font = false;
2004                         }
2005                         basefont = getLayoutFont(bparams, outerfont);
2006                         running_font = basefont;
2007
2008                         column += Changes::latexMarkChange(os, bparams, runningChange, change);
2009                         runningChange = change;
2010                 }
2011
2012                 // do not output text which is marked deleted
2013                 // if change tracking output is disabled
2014                 if (!bparams.outputChanges && change.type == Change::DELETED) {
2015                         continue;
2016                 }
2017
2018                 ++column;
2019
2020                 value_type const c = getChar(i);
2021
2022                 // Fully instantiated font
2023                 Font const font = getFont(bparams, i, outerfont);
2024
2025                 Font const last_font = running_font;
2026
2027                 // Do we need to close the previous font?
2028                 if (open_font &&
2029                     (font != running_font ||
2030                      font.language() != running_font.language()))
2031                 {
2032                         column += running_font.latexWriteEndChanges(
2033                                         os, bparams, runparams, basefont,
2034                                         (i == body_pos-1) ? basefont : font);
2035                         running_font = basefont;
2036                         open_font = false;
2037                 }
2038
2039                 // Switch file encoding if necessary
2040                 if (runparams.encoding->package() == Encoding::inputenc &&
2041                     font.language()->encoding()->package() == Encoding::inputenc) {
2042                         int const count = switchEncoding(os, bparams,
2043                                         runparams.moving_arg, *(runparams.encoding),
2044                                         *(font.language()->encoding()));
2045                         if (count > 0) {
2046                                 column += count;
2047                                 runparams.encoding = font.language()->encoding();
2048                         }
2049                 }
2050
2051                 // Do we need to change font?
2052                 if ((font != running_font ||
2053                      font.language() != running_font.language()) &&
2054                         i != body_pos - 1)
2055                 {
2056                         column += font.latexWriteStartChanges(os, bparams,
2057                                                               runparams, basefont,
2058                                                               last_font);
2059                         running_font = font;
2060                         open_font = true;
2061                 }
2062
2063                 if (c == ' ') {
2064                         // Do not print the separation of the optional argument
2065                         // if style->pass_thru is false. This works because
2066                         // simpleTeXSpecialChars ignores spaces if
2067                         // style->pass_thru is false.
2068                         if (i != body_pos - 1) {
2069                                 if (pimpl_->simpleTeXBlanks(
2070                                                 *(runparams.encoding), os, texrow,
2071                                                 i, column, font, *style))
2072                                         // A surrogate pair was output. We
2073                                         // must not call simpleTeXSpecialChars
2074                                         // in this iteration, since
2075                                         // simpleTeXBlanks incremented i, and
2076                                         // simpleTeXSpecialChars would output
2077                                         // the combining character again.
2078                                         continue;
2079                         }
2080                 }
2081
2082                 OutputParams rp = runparams;
2083                 rp.free_spacing = style->free_spacing;
2084                 rp.local_font = &font;
2085                 rp.intitle = style->intitle;
2086                 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2087                                         texrow, rp, running_font,
2088                                         basefont, outerfont, open_font,
2089                                         runningChange, *style, i, column, c);
2090         }
2091
2092         // If we have an open font definition, we have to close it
2093         if (open_font) {
2094 #ifdef FIXED_LANGUAGE_END_DETECTION
2095                 if (next_) {
2096                         running_font
2097                                 .latexWriteEndChanges(os, bparams, runparams,
2098                                         basefont,
2099                                         next_->getFont(bparams, 0, outerfont));
2100                 } else {
2101                         running_font.latexWriteEndChanges(os, bparams,
2102                                         runparams, basefont, basefont);
2103                 }
2104 #else
2105 #ifdef WITH_WARNINGS
2106 //#warning For now we ALWAYS have to close the foreign font settings if they are
2107 //#warning there as we start another \selectlanguage with the next paragraph if
2108 //#warning we are in need of this. This should be fixed sometime (Jug)
2109 #endif
2110                 running_font.latexWriteEndChanges(os, bparams, runparams,
2111                                 basefont, basefont);
2112 #endif
2113         }
2114
2115         column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2116
2117         // Needed if there is an optional argument but no contents.
2118         if (body_pos > 0 && body_pos == size()) {
2119                 os << "}]~";
2120                 return_value = false;
2121         }
2122
2123         if (!asdefault) {
2124                 column += endTeXParParams(bparams, os, texrow,
2125                                           runparams.moving_arg);
2126         }
2127
2128         LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2129         return return_value;
2130 }
2131
2132
2133 namespace {
2134
2135 enum PAR_TAG {
2136         PAR_NONE=0,
2137         TT = 1,
2138         SF = 2,
2139         BF = 4,
2140         IT = 8,
2141         SL = 16,
2142         EM = 32
2143 };
2144
2145
2146 string tag_name(PAR_TAG const & pt) {
2147         switch (pt) {
2148         case PAR_NONE: return "!-- --";
2149         case TT: return "tt";
2150         case SF: return "sf";
2151         case BF: return "bf";
2152         case IT: return "it";
2153         case SL: return "sl";
2154         case EM: return "em";
2155         }
2156         return "";
2157 }
2158
2159
2160 inline
2161 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2162 {
2163         p1 = static_cast<PAR_TAG>(p1 | p2);
2164 }
2165
2166
2167 inline
2168 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2169 {
2170         p1 = static_cast<PAR_TAG>(p1 & ~p2);
2171 }
2172
2173 } // anon
2174
2175
2176 bool Paragraph::emptyTag() const
2177 {
2178         for (pos_type i = 0; i < size(); ++i) {
2179                 if (isInset(i)) {
2180                         Inset const * inset = getInset(i);
2181                         Inset::Code lyx_code = inset->lyxCode();
2182                         if (lyx_code != Inset::TOC_CODE &&
2183                             lyx_code != Inset::INCLUDE_CODE &&
2184                             lyx_code != Inset::GRAPHICS_CODE &&
2185                             lyx_code != Inset::ERT_CODE &&
2186                             lyx_code != Inset::LISTINGS_CODE &&
2187                             lyx_code != Inset::FLOAT_CODE &&
2188                             lyx_code != Inset::TABULAR_CODE) {
2189                                 return false;
2190                         }
2191                 } else {
2192                         value_type c = getChar(i);
2193                         if (c != ' ' && c != '\t')
2194                                 return false;
2195                 }
2196         }
2197         return true;
2198 }
2199
2200
2201 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2202 {
2203         for (pos_type i = 0; i < size(); ++i) {
2204                 if (isInset(i)) {
2205                         Inset const * inset = getInset(i);
2206                         Inset::Code lyx_code = inset->lyxCode();
2207                         if (lyx_code == Inset::LABEL_CODE) {
2208                                 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2209                                 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2210                         }
2211                 }
2212
2213         }
2214         return string();
2215 }
2216
2217
2218 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2219 {
2220         pos_type i;
2221         for (i = 0; i < size(); ++i) {
2222                 if (isInset(i)) {
2223                         Inset const * inset = getInset(i);
2224                         inset->docbook(buf, os, runparams);
2225                 } else {
2226                         value_type c = getChar(i);
2227                         if (c == ' ')
2228                                 break;
2229                         os << sgml::escapeChar(c);
2230                 }
2231         }
2232         return i;
2233 }
2234
2235
2236 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2237 {
2238         Font font_old;
2239
2240         for (pos_type i = initial; i < size(); ++i) {
2241                 Font font = getFont(buf.params(), i, outerfont);
2242                 if (isInset(i))
2243                         return false;
2244                 if (i != initial && font != font_old)
2245                         return false;
2246                 font_old = font;
2247         }
2248
2249         return true;
2250 }
2251
2252
2253 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2254                                     odocstream & os,
2255                                     OutputParams const & runparams,
2256                                     Font const & outerfont,
2257                                     pos_type initial) const
2258 {
2259         bool emph_flag = false;
2260
2261         Layout_ptr const & style = layout();
2262         Font font_old =
2263                 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2264
2265         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2266                 os << "]]>";
2267
2268         // parsing main loop
2269         for (pos_type i = initial; i < size(); ++i) {
2270                 Font font = getFont(buf.params(), i, outerfont);
2271
2272                 // handle <emphasis> tag
2273                 if (font_old.emph() != font.emph()) {
2274                         if (font.emph() == Font::ON) {
2275                                 os << "<emphasis>";
2276                                 emph_flag = true;
2277                         } else if (i != initial) {
2278                                 os << "</emphasis>";
2279                                 emph_flag = false;
2280                         }
2281                 }
2282
2283                 if (isInset(i)) {
2284                         Inset const * inset = getInset(i);
2285                         inset->docbook(buf, os, runparams);
2286                 } else {
2287                         value_type c = getChar(i);
2288
2289                         if (style->pass_thru)
2290                                 os.put(c);
2291                         else
2292                                 os << sgml::escapeChar(c);
2293                 }
2294                 font_old = font;
2295         }
2296
2297         if (emph_flag) {
2298                 os << "</emphasis>";
2299         }
2300
2301         if (style->free_spacing)
2302                 os << '\n';
2303         if (style->pass_thru && !onlyText(buf, outerfont, initial))
2304                 os << "<![CDATA[";
2305 }
2306
2307
2308 bool Paragraph::isNewline(pos_type pos) const
2309 {
2310         return isInset(pos)
2311                 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2312 }
2313
2314
2315 bool Paragraph::isLineSeparator(pos_type pos) const
2316 {
2317         value_type const c = getChar(pos);
2318         return isLineSeparatorChar(c)
2319                 || (c == Paragraph::META_INSET && getInset(pos) &&
2320                 getInset(pos)->isLineSeparator());
2321 }
2322
2323
2324 /// Used by the spellchecker
2325 bool Paragraph::isLetter(pos_type pos) const
2326 {
2327         if (isInset(pos))
2328                 return getInset(pos)->isLetter();
2329         else {
2330                 value_type const c = getChar(pos);
2331                 return isLetterChar(c) || isDigit(c);
2332         }
2333 }
2334
2335
2336 Language const *
2337 Paragraph::getParLanguage(BufferParams const & bparams) const
2338 {
2339         if (!empty())
2340                 return getFirstFontSettings(bparams).language();
2341 #ifdef WITH_WARNINGS
2342 #warning FIXME we should check the prev par as well (Lgb)
2343 #endif
2344         return bparams.language;
2345 }
2346
2347
2348 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2349 {
2350         return lyxrc.rtl_support
2351                 && getParLanguage(bparams)->rightToLeft()
2352                 && ownerCode() != Inset::ERT_CODE
2353                 && ownerCode() != Inset::LISTINGS_CODE;
2354 }
2355
2356
2357 void Paragraph::changeLanguage(BufferParams const & bparams,
2358                                Language const * from, Language const * to)
2359 {
2360         // change language including dummy font change at the end
2361         for (pos_type i = 0; i <= size(); ++i) {
2362                 Font font = getFontSettings(bparams, i);
2363                 if (font.language() == from) {
2364                         font.setLanguage(to);
2365                         setFont(i, font);
2366                 }
2367         }
2368 }
2369
2370
2371 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2372 {
2373         Language const * doc_language = bparams.language;
2374         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2375         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2376
2377         for (; cit != end; ++cit)
2378                 if (cit->font().language() != ignore_language &&
2379                     cit->font().language() != latex_language &&
2380                     cit->font().language() != doc_language)
2381                         return true;
2382         return false;
2383 }
2384
2385
2386 // Convert the paragraph to a string.
2387 // Used for building the table of contents
2388 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2389 {
2390         return asString(buffer, 0, size(), label);
2391 }
2392
2393
2394 docstring const Paragraph::asString(Buffer const & buffer,
2395                                  pos_type beg, pos_type end, bool label) const
2396 {
2397
2398         odocstringstream os;
2399
2400         if (beg == 0 && label && !params().labelString().empty())
2401                 os << params().labelString() << ' ';
2402
2403         for (pos_type i = beg; i < end; ++i) {
2404                 value_type const c = getChar(i);
2405                 if (isPrintable(c))
2406                         os.put(c);
2407                 else if (c == META_INSET)
2408                         getInset(i)->textString(buffer, os);
2409         }
2410
2411         return os.str();
2412 }
2413
2414
2415 void Paragraph::setInsetOwner(Inset * inset)
2416 {
2417         pimpl_->inset_owner = inset;
2418 }
2419
2420
2421 Change const & Paragraph::lookupChange(pos_type pos) const
2422 {
2423         BOOST_ASSERT(pos <= size());
2424         return pimpl_->lookupChange(pos);
2425 }
2426
2427
2428 bool Paragraph::isChanged(pos_type start, pos_type end) const
2429 {
2430         return pimpl_->isChanged(start, end);
2431 }
2432
2433
2434 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2435 {
2436         return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2437 }
2438
2439
2440 void Paragraph::setChange(Change const & change)
2441 {
2442         pimpl_->setChange(change);
2443 }
2444
2445
2446 void Paragraph::setChange(pos_type pos, Change const & change)
2447 {
2448         pimpl_->setChange(pos, change);
2449 }
2450
2451
2452 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2453 {
2454         return pimpl_->acceptChanges(bparams, start, end);
2455 }
2456
2457
2458 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2459 {
2460         return pimpl_->rejectChanges(bparams, start, end);
2461 }
2462
2463
2464 int Paragraph::id() const
2465 {
2466         return pimpl_->id_;
2467 }
2468
2469
2470 Layout_ptr const & Paragraph::layout() const
2471 {
2472         return layout_;
2473 }
2474
2475
2476 void Paragraph::layout(Layout_ptr const & new_layout)
2477 {
2478         layout_ = new_layout;
2479 }
2480
2481
2482 Inset * Paragraph::inInset() const
2483 {
2484         return pimpl_->inset_owner;
2485 }
2486
2487
2488 Inset::Code Paragraph::ownerCode() const
2489 {
2490         return pimpl_->inset_owner
2491                 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2492 }
2493
2494
2495 ParagraphParameters & Paragraph::params()
2496 {
2497         return pimpl_->params;
2498 }
2499
2500
2501 ParagraphParameters const & Paragraph::params() const
2502 {
2503         return pimpl_->params;
2504 }
2505
2506
2507 bool Paragraph::isFreeSpacing() const
2508 {
2509         if (layout()->free_spacing)
2510                 return true;
2511
2512         // for now we just need this, later should we need this in some
2513         // other way we can always add a function to Inset too.
2514         return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2515 }
2516
2517
2518 bool Paragraph::allowEmpty() const
2519 {
2520         if (layout()->keepempty)
2521                 return true;
2522         return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2523 }
2524
2525
2526 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2527 {
2528         if (!Encodings::is_arabic(c))
2529                 return c;
2530
2531         value_type prev_char = ' ';
2532         value_type next_char = ' ';
2533
2534         for (pos_type i = pos - 1; i >= 0; --i) {
2535                 value_type const par_char = getChar(i);
2536                 if (!Encodings::isComposeChar_arabic(par_char)) {
2537                         prev_char = par_char;
2538                         break;
2539                 }
2540         }
2541
2542         for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2543                 value_type const par_char = getChar(i);
2544                 if (!Encodings::isComposeChar_arabic(par_char)) {
2545                         next_char = par_char;
2546                         break;
2547                 }
2548         }
2549
2550         if (Encodings::is_arabic(next_char)) {
2551                 if (Encodings::is_arabic(prev_char) &&
2552                         !Encodings::is_arabic_special(prev_char))
2553                         return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2554                 else
2555                         return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2556         } else {
2557                 if (Encodings::is_arabic(prev_char) &&
2558                         !Encodings::is_arabic_special(prev_char))
2559                         return Encodings::transformChar(c, Encodings::FORM_FINAL);
2560                 else
2561                         return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2562         }
2563 }
2564
2565
2566 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2567 {
2568         if (!isHfill(pos))
2569                 return false;
2570
2571         BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2572
2573         // expand at the end of a row only if there is another hfill on the same row
2574         if (pos == row.endpos() - 1) {
2575                 for (pos_type i = row.pos(); i < pos; i++) {
2576                         if (isHfill(i))
2577                                 return true;
2578                 }
2579                 return false;
2580         }
2581
2582         // expand at the beginning of a row only if it is the first row of a paragraph
2583         if (pos == row.pos()) {
2584                 return pos == 0;
2585         }
2586
2587         // do not expand in some labels
2588         if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2589                 return false;
2590
2591         // if there is anything between the first char of the row and
2592         // the specified position that is neither a newline nor an hfill,
2593         // the hfill will be expanded, otherwise it won't
2594         for (pos_type i = row.pos(); i < pos; i++) {
2595                 if (!isNewline(i) && !isHfill(i))
2596                         return true;
2597         }
2598         return false;
2599 }
2600
2601
2602 int Paragraph::checkBiblio(bool track_changes)
2603 {
2604         //FIXME From JS:
2605         //This is getting more and more a mess. ...We really should clean
2606         //up this bibitem issue for 1.6. See also bug 2743.
2607
2608         // Add bibitem insets if necessary
2609         if (layout()->labeltype != LABEL_BIBLIO)
2610                 return 0;
2611
2612         bool hasbibitem = !insetlist.empty()
2613                 // Insist on it being in pos 0
2614                 && getChar(0) == Paragraph::META_INSET
2615                 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2616
2617         docstring oldkey;
2618         docstring oldlabel;
2619
2620         // remove a bibitem in pos != 0
2621         // restore it later in pos 0 if necessary
2622         // (e.g. if a user inserts contents _before_ the item)
2623         // we're assuming there's only one of these, which there
2624         // should be.
2625         int erasedInsetPosition = -1;
2626         InsetList::iterator it = insetlist.begin();
2627         InsetList::iterator end = insetlist.end();
2628         for (; it != end; ++it)
2629                 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2630                     && it->pos > 0) {
2631                         InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2632                         oldkey = olditem->getParam("key");
2633                         oldlabel = olditem->getParam("label");
2634                         erasedInsetPosition = it->pos;
2635                         eraseChar(erasedInsetPosition, track_changes);
2636                         break;
2637         }
2638
2639         //There was an InsetBibitem at the beginning, and we didn't
2640         //have to erase one.
2641         if (hasbibitem && erasedInsetPosition < 0)
2642                         return 0;
2643
2644         //There was an InsetBibitem at the beginning and we did have to
2645         //erase one. So we give its properties to the beginning inset.
2646         if (hasbibitem) {
2647                 InsetBibitem * inset =
2648                         static_cast<InsetBibitem *>(insetlist.begin()->inset);
2649                 if (!oldkey.empty())
2650                         inset->setParam("key", oldkey);
2651                 inset->setParam("label", oldlabel);
2652                 return -erasedInsetPosition;
2653         }
2654
2655         //There was no inset at the beginning, so we need to create one with
2656         //the key and label of the one we erased.
2657         InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2658         // restore values of previously deleted item in this par.
2659         if (!oldkey.empty())
2660                 inset->setParam("key", oldkey);
2661         inset->setParam("label", oldlabel);
2662         insertInset(0, static_cast<Inset *>(inset),
2663                     Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2664
2665         return 1;
2666 }
2667
2668 } // namespace lyx