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