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