]> git.lyx.org Git - lyx.git/blob - src/Paragraph.cpp
Fixed some lines that were too long. It compiled afterwards.
[lyx.git] / src / Paragraph.cpp
1 /**
2  * \file Paragraph.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author Angus Leeming
10  * \author John Levon
11  * \author André Pönitz
12  * \author Dekel Tsur
13  * \author Jürgen Vigna
14  *
15  * Full author contact details are available in file CREDITS.
16  */
17
18 #include <config.h>
19
20 #include "Paragraph.h"
21
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "Counters.h"
25 #include "Encoding.h"
26 #include "debug.h"
27 #include "gettext.h"
28 #include "Language.h"
29 #include "LaTeXFeatures.h"
30 #include "Color.h"
31 #include "Length.h"
32 #include "Font.h"
33 #include "LyXRC.h"
34 #include "Row.h"
35 #include "Messages.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "paragraph_funcs.h"
39 #include "ParagraphParameters.h"
40 #include "rowpainter.h"
41 #include "sgml.h"
42 #include "TexRow.h"
43 #include "VSpace.h"
44
45 #include "frontends/alert.h"
46 #include "frontends/FontMetrics.h"
47
48 #include "insets/InsetBibitem.h"
49 #include "insets/InsetOptArg.h"
50
51 #include "support/lstrings.h"
52 #include "support/textutils.h"
53 #include "support/convert.h"
54 #include "support/unicode.h"
55
56 #include <boost/bind.hpp>
57 #include <boost/next_prior.hpp>
58
59 #include <algorithm>
60 #include <sstream>
61
62 using std::distance;
63 using std::endl;
64 using std::string;
65 using std::ostream;
66
67 namespace lyx {
68
69 using support::contains;
70 using support::suffixIs;
71 using support::rsplit;
72
73
74 /////////////////////////////////////////////////////////////////////
75 //
76 // Paragraph::Pimpl
77 //
78 /////////////////////////////////////////////////////////////////////
79
80 class Encoding;
81 class Layout;
82
83
84 class Paragraph::Pimpl {
85 public:
86         ///
87         Pimpl(Paragraph * owner);
88         /// "Copy constructor"
89         Pimpl(Pimpl const &, Paragraph * owner);
90
91         //
92         // Change tracking
93         //
94         /// look up change at given pos
95         Change const & lookupChange(pos_type pos) const;
96         /// is there a change within the given range ?
97         bool isChanged(pos_type start, pos_type end) const;
98         /// will the paragraph be physically merged with the next
99         /// one if the imaginary end-of-par character is logically deleted?
100         bool isMergedOnEndOfParDeletion(bool trackChanges) const;
101         /// set change for the entire par
102         void setChange(Change const & change);
103         /// set change at given pos
104         void setChange(pos_type pos, Change const & change);
105         /// accept changes within the given range
106         void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
107         /// reject changes within the given range
108         void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
109
110         ///
111         value_type getChar(pos_type pos) const;
112         ///
113         void insertChar(pos_type pos, value_type c, Change const & change);
114         ///
115         void insertInset(pos_type pos, Inset * inset, Change const & change);
116         /// (logically) erase the char at pos; return true if it was actually erased
117         bool eraseChar(pos_type pos, bool trackChanges);
118         /// (logically) erase the given range; return the number of chars actually erased
119         int eraseChars(pos_type start, pos_type end, bool trackChanges);
120         ///
121         Inset * inset_owner;
122
123         /** A font entry covers a range of positions. Notice that the
124             entries in the list are inserted in random order.
125             I don't think it's worth the effort to implement a more effective
126             datastructure, because the number of different fonts in a paragraph
127             is limited. (Asger)
128             Nevertheless, I decided to store fontlist using a sorted vector:
129             fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
130             pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
131             and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
132             (font_1 covers the chars 0,...,pos_1) (Dekel)
133         */
134         class FontTable  {
135         public:
136                 ///
137                 FontTable(pos_type p, Font const & f)
138                         : pos_(p), font_(f)
139                 {}
140                 ///
141                 pos_type pos() const { return pos_; }
142                 ///
143                 void pos(pos_type p) { pos_ = p; }
144                 ///
145                 Font const & font() const { return font_; }
146                 ///
147                 void font(Font const & f) { font_ = f;}
148         private:
149                 /// End position of paragraph this font attribute covers
150                 pos_type pos_;
151                 /** Font. Interpretation of the font values:
152                     If a value is Font::INHERIT_*, it means that the font
153                     attribute is inherited from either the layout of this
154                     paragraph or, in the case of nested paragraphs, from the
155                     layout in the environment one level up until completely
156                     resolved.
157                     The values Font::IGNORE_* and Font::TOGGLE are NOT
158                     allowed in these font tables.
159                 */
160                 Font font_;
161         };
162         ///
163         friend class matchFT;
164         ///
165         class matchFT {
166         public:
167                 /// used by lower_bound and upper_bound
168                 int operator()(FontTable const & a, FontTable const & b) const {
169                         return a.pos() < b.pos();
170                 }
171         };
172
173         ///
174         typedef std::vector<FontTable> FontList;
175         ///
176         FontList fontlist;
177
178         /// Output the surrogate pair formed by \p c and \p next to \p os.
179         /// \return the number of characters written.
180         int latexSurrogatePair(odocstream & os, value_type c, value_type next,
181                                Encoding const &);
182         /// Output a space in appropriate formatting (or a surrogate pair
183         /// if the next character is a combining character).
184         /// \return whether a surrogate pair was output.
185         bool simpleTeXBlanks(Encoding const &,
186                              odocstream &, TexRow & texrow,
187                              pos_type & i,
188                              unsigned int & column,
189                              Font const & font,
190                              Layout const & style);
191         ///
192         void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
193                                    odocstream &,
194                                    TexRow & texrow, OutputParams &,
195                                    Font & running_font,
196                                    Font & basefont,
197                                    Font const & outerfont,
198                                    bool & open_font,
199                                    Change & running_change,
200                                    Layout const & style,
201                                    pos_type & i,
202                                    unsigned int & column, value_type const c);
203
204         ///
205         void validate(LaTeXFeatures & features,
206                       Layout const & layout) const;
207
208         ///
209         unsigned int id_;
210         ///
211         static unsigned int paragraph_id;
212         ///
213         ParagraphParameters params;
214
215 //private:
216         ///
217         pos_type size() const { return owner_->size(); }
218         /// match a string against a particular point in the paragraph
219         bool isTextAt(std::string const & str, pos_type pos) const;
220
221         /// for recording and looking up changes
222         Changes changes_;
223
224         /// Who owns us?
225         Paragraph * owner_;
226 };
227
228
229
230
231 using std::endl;
232 using std::upper_bound;
233 using std::lower_bound;
234 using std::string;
235
236
237 // Initialization of the counter for the paragraph id's,
238 unsigned int Paragraph::Pimpl::paragraph_id = 0;
239
240 namespace {
241
242 struct special_phrase {
243         string phrase;
244         docstring macro;
245         bool builtin;
246 };
247
248 special_phrase const special_phrases[] = {
249         { "LyX", from_ascii("\\LyX{}"), false },
250         { "TeX", from_ascii("\\TeX{}"), true },
251         { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
252         { "LaTeX", from_ascii("\\LaTeX{}"), true },
253 };
254
255 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
256
257 } // namespace anon
258
259
260 Paragraph::Pimpl::Pimpl(Paragraph * owner)
261         : owner_(owner)
262 {
263         inset_owner = 0;
264         id_ = paragraph_id++;
265 }
266
267
268 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
269         : params(p.params), changes_(p.changes_), owner_(owner)
270 {
271         inset_owner = p.inset_owner;
272         fontlist = p.fontlist;
273         id_ = paragraph_id++;
274 }
275
276
277 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
278 {
279         BOOST_ASSERT(start >= 0 && start <= size());
280         BOOST_ASSERT(end > start && end <= size() + 1);
281
282         return changes_.isChanged(start, end);
283 }
284
285
286 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
287         // keep the logic here in sync with the logic of eraseChars()
288
289         if (!trackChanges) {
290                 return true;
291         }
292
293         Change change = changes_.lookup(size());
294
295         return change.type == Change::INSERTED && change.author == 0;
296 }
297
298
299 void Paragraph::Pimpl::setChange(Change const & change)
300 {
301         // beware of the imaginary end-of-par character!
302         changes_.set(change, 0, size() + 1);
303
304         /*
305          * Propagate the change recursively - but not in case of DELETED!
306          *
307          * Imagine that your co-author makes changes in an existing inset. He
308          * sends your document to you and you come to the conclusion that the
309          * inset should go completely. If you erase it, LyX must not delete all
310          * text within the inset. Otherwise, the change tracked insertions of
311          * your co-author get lost and there is no way to restore them later.
312          *
313          * Conclusion: An inset's content should remain untouched if you delete it
314          */
315
316         if (change.type != Change::DELETED) {
317                 for (pos_type pos = 0; pos < size(); ++pos) {
318                         if (owner_->isInset(pos)) {
319                                 owner_->getInset(pos)->setChange(change);
320                         }
321                 }
322         }
323 }
324
325
326 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
327 {
328         BOOST_ASSERT(pos >= 0 && pos <= size());
329
330         changes_.set(change, pos);
331
332         // see comment in setChange(Change const &) above
333
334         if (change.type != Change::DELETED &&
335             pos < size() && owner_->isInset(pos)) {
336                 owner_->getInset(pos)->setChange(change);
337         }
338 }
339
340
341 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
342 {
343         BOOST_ASSERT(pos >= 0 && pos <= size());
344
345         return changes_.lookup(pos);
346 }
347
348
349 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
350 {
351         BOOST_ASSERT(start >= 0 && start <= size());
352         BOOST_ASSERT(end > start && end <= size() + 1);
353
354         for (pos_type pos = start; pos < end; ++pos) {
355                 switch (lookupChange(pos).type) {
356                         case Change::UNCHANGED:
357                                 // accept changes in nested inset
358                                 if (pos < size() && owner_->isInset(pos)) {
359                                         owner_->getInset(pos)->acceptChanges(bparams);
360                                 }
361
362                                 break;
363
364                         case Change::INSERTED:
365                                 changes_.set(Change(Change::UNCHANGED), pos);
366                                 // also accept changes in nested inset
367                                 if (pos < size() && owner_->isInset(pos)) {
368                                         owner_->getInset(pos)->acceptChanges(bparams);
369                                 }
370                                 break;
371
372                         case Change::DELETED:
373                                 // Suppress access to non-existent
374                                 // "end-of-paragraph char"
375                                 if (pos < size()) {
376                                         eraseChar(pos, false);
377                                         --end;
378                                         --pos;
379                                 }
380                                 break;
381                 }
382
383         }
384 }
385
386
387 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
388 {
389         BOOST_ASSERT(start >= 0 && start <= size());
390         BOOST_ASSERT(end > start && end <= size() + 1);
391
392         for (pos_type pos = start; pos < end; ++pos) {
393                 switch (lookupChange(pos).type) {
394                         case Change::UNCHANGED:
395                                 // reject changes in nested inset
396                                 if (pos < size() && owner_->isInset(pos)) {
397                                         owner_->getInset(pos)->rejectChanges(bparams);
398                                 }
399                                 break;
400
401                         case Change::INSERTED:
402                                 // Suppress access to non-existent
403                                 // "end-of-paragraph char"
404                                 if (pos < size()) {
405                                         eraseChar(pos, false);
406                                         --end;
407                                         --pos;
408                                 }
409                                 break;
410
411                         case Change::DELETED:
412                                 changes_.set(Change(Change::UNCHANGED), pos);
413
414                                 // Do NOT reject changes within a deleted inset!
415                                 // There may be insertions of a co-author inside of it!
416
417                                 break;
418                 }
419         }
420 }
421
422
423 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
424 {
425         BOOST_ASSERT(pos >= 0 && pos <= size());
426
427         return owner_->getChar(pos);
428 }
429
430
431 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
432 {
433         BOOST_ASSERT(pos >= 0 && pos <= size());
434
435         // track change
436         changes_.insert(change, pos);
437
438         // This is actually very common when parsing buffers (and
439         // maybe inserting ascii text)
440         if (pos == size()) {
441                 // when appending characters, no need to update tables
442                 owner_->text_.push_back(c);
443                 return;
444         }
445
446         owner_->text_.insert(owner_->text_.begin() + pos, c);
447
448         // Update the font table.
449         FontTable search_font(pos, Font());
450         for (FontList::iterator it
451               = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
452              it != fontlist.end(); ++it)
453         {
454                 it->pos(it->pos() + 1);
455         }
456
457         // Update the insets
458         owner_->insetlist.increasePosAfterPos(pos);
459 }
460
461
462 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
463                                    Change const & change)
464 {
465         BOOST_ASSERT(inset);
466         BOOST_ASSERT(pos >= 0 && pos <= size());
467
468         insertChar(pos, META_INSET, change);
469         BOOST_ASSERT(owner_->text_[pos] == META_INSET);
470
471         // Add a new entry in the insetlist.
472         owner_->insetlist.insert(inset, pos);
473 }
474
475
476 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
477 {
478         BOOST_ASSERT(pos >= 0 && pos <= size());
479
480         // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
481
482         if (trackChanges) {
483                 Change change = changes_.lookup(pos);
484
485                 // set the character to DELETED if
486                 //  a) it was previously unchanged or
487                 //  b) it was inserted by a co-author
488
489                 if (change.type == Change::UNCHANGED ||
490                     (change.type == Change::INSERTED && change.author != 0)) {
491                         setChange(pos, Change(Change::DELETED));
492                         return false;
493                 }
494
495                 if (change.type == Change::DELETED)
496                         return false;
497         }
498
499         // Don't physically access the imaginary end-of-paragraph character.
500         // eraseChar() can only mark it as DELETED. A physical deletion of
501         // end-of-par must be handled externally.
502         if (pos == size()) {
503                 return false;
504         }
505
506         // track change
507         changes_.erase(pos);
508
509         // if it is an inset, delete the inset entry
510         if (owner_->text_[pos] == Paragraph::META_INSET) {
511                 owner_->insetlist.erase(pos);
512         }
513
514         owner_->text_.erase(owner_->text_.begin() + pos);
515
516         // Erase entries in the tables.
517         FontTable search_font(pos, Font());
518
519         FontList::iterator it =
520                 lower_bound(fontlist.begin(),
521                             fontlist.end(),
522                             search_font, matchFT());
523         if (it != fontlist.end() && it->pos() == pos &&
524             (pos == 0 ||
525              (it != fontlist.begin()
526               && boost::prior(it)->pos() == pos - 1))) {
527                 // If it is a multi-character font
528                 // entry, we just make it smaller
529                 // (see update below), otherwise we
530                 // should delete it.
531                 unsigned int const i = it - fontlist.begin();
532                 fontlist.erase(fontlist.begin() + i);
533                 it = fontlist.begin() + i;
534                 if (i > 0 && i < fontlist.size() &&
535                     fontlist[i - 1].font() == fontlist[i].font()) {
536                         fontlist.erase(fontlist.begin() + i - 1);
537                         it = fontlist.begin() + i - 1;
538                 }
539         }
540
541         // Update all other entries
542         FontList::iterator fend = fontlist.end();
543         for (; it != fend; ++it)
544                 it->pos(it->pos() - 1);
545
546         // Update the insetlist
547         owner_->insetlist.decreasePosAfterPos(pos);
548
549         return true;
550 }
551
552
553 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
554 {
555         BOOST_ASSERT(start >= 0 && start <= size());
556         BOOST_ASSERT(end >= start && end <= size() + 1);
557
558         pos_type i = start;
559         for (pos_type count = end - start; count; --count) {
560                 if (!eraseChar(i, trackChanges))
561                         ++i;
562         }
563         return end - i;
564 }
565
566
567 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
568                 value_type next, Encoding const & encoding)
569 {
570         // Writing next here may circumvent a possible font change between
571         // c and next. Since next is only output if it forms a surrogate pair
572         // with c we can ignore this:
573         // A font change inside a surrogate pair does not make sense and is
574         // hopefully impossible to input.
575         // FIXME: change tracking
576         // Is this correct WRT change tracking?
577         docstring const latex1 = encoding.latexChar(next);
578         docstring const latex2 = encoding.latexChar(c);
579         os << latex1 << '{' << latex2 << '}';
580         return latex1.length() + latex2.length() + 2;
581 }
582
583
584 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
585                                        odocstream & os, TexRow & texrow,
586                                        pos_type & i,
587                                        unsigned int & column,
588                                        Font const & font,
589                                        Layout const & style)
590 {
591         if (style.pass_thru)
592                 return false;
593
594         if (i < size() - 1) {
595                 char_type next = getChar(i + 1);
596                 if (Encodings::isCombiningChar(next)) {
597                         // This space has an accent, so we must always output it.
598                         column += latexSurrogatePair(os, ' ', next, encoding) - 1;
599                         ++i;
600                         return true;
601                 }
602         }
603
604         if (lyxrc.plaintext_linelen > 0
605             && column > lyxrc.plaintext_linelen
606             && i
607             && getChar(i - 1) != ' '
608             && (i < size() - 1)
609             // same in FreeSpacing mode
610             && !owner_->isFreeSpacing()
611             // In typewriter mode, we want to avoid
612             // ! . ? : at the end of a line
613             && !(font.family() == Font::TYPEWRITER_FAMILY
614                  && (getChar(i - 1) == '.'
615                      || getChar(i - 1) == '?'
616                      || getChar(i - 1) == ':'
617                      || getChar(i - 1) == '!'))) {
618                 os << '\n';
619                 texrow.newline();
620                 texrow.start(owner_->id(), i + 1);
621                 column = 0;
622         } else if (style.free_spacing) {
623                 os << '~';
624         } else {
625                 os << ' ';
626         }
627         return false;
628 }
629
630
631 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
632 {
633         pos_type const len = str.length();
634
635         // is the paragraph large enough?
636         if (pos + len > size())
637                 return false;
638
639         // does the wanted text start at point?
640         for (string::size_type i = 0; i < str.length(); ++i) {
641                 // Caution: direct comparison of characters works only
642                 // because str is pure ASCII.
643                 if (str[i] != owner_->text_[pos + i])
644                         return false;
645         }
646
647         // is there a font change in middle of the word?
648         FontList::const_iterator cit = fontlist.begin();
649         FontList::const_iterator end = fontlist.end();
650         for (; cit != end; ++cit) {
651                 if (cit->pos() >= pos)
652                         break;
653         }
654         if (cit != end && pos + len - 1 > cit->pos())
655                 return false;
656
657         return true;
658 }
659
660
661 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
662                                              BufferParams const & bparams,
663                                              odocstream & os,
664                                              TexRow & texrow,
665                                              OutputParams & runparams,
666                                              Font & running_font,
667                                              Font & basefont,
668                                              Font const & outerfont,
669                                              bool & open_font,
670                                              Change & running_change,
671                                              Layout const & style,
672                                              pos_type & i,
673                                              unsigned int & column,
674                                              value_type const c)
675 {
676         if (style.pass_thru) {
677                 if (c != Paragraph::META_INSET) {
678                         if (c != '\0')
679                                 // FIXME UNICODE: This can fail if c cannot
680                                 // be encoded in the current encoding.
681                                 os.put(c);
682                 } else
683                         owner_->getInset(i)->plaintext(buf, os, runparams);
684                 return;
685         }
686
687         // Two major modes:  LaTeX or plain
688         // Handle here those cases common to both modes
689         // and then split to handle the two modes separately.
690         switch (c) {
691         case Paragraph::META_INSET: {
692                 Inset * inset = owner_->getInset(i);
693
694                 // FIXME: remove this check
695                 if (!inset)
696                         break;
697
698                 // FIXME: move this to InsetNewline::latex
699                 if (inset->lyxCode() == Inset::NEWLINE_CODE) {
700                         // newlines are handled differently here than
701                         // the default in simpleTeXSpecialChars().
702                         if (!style.newline_allowed) {
703                                 os << '\n';
704                         } else {
705                                 if (open_font) {
706                                         column += running_font.latexWriteEndChanges(
707                                                 os, bparams, runparams,
708                                                 basefont, basefont);
709                                         open_font = false;
710                                 }
711
712                                 if (running_font.family() == Font::TYPEWRITER_FAMILY)
713                                         os << '~';
714
715                                 basefont = owner_->getLayoutFont(bparams, outerfont);
716                                 running_font = basefont;
717
718                                 if (runparams.moving_arg)
719                                         os << "\\protect ";
720
721                                 os << "\\\\\n";
722                         }
723                         texrow.newline();
724                         texrow.start(owner_->id(), i + 1);
725                         column = 0;
726                         break;
727                 }
728
729                 if (lookupChange(i).type == Change::DELETED) {
730                         if( ++runparams.inDeletedInset == 1)
731                                 runparams.changeOfDeletedInset = lookupChange(i);
732                 }
733
734                 if (inset->canTrackChanges()) {
735                         column += Changes::latexMarkChange(os, bparams, running_change,
736                                 Change(Change::UNCHANGED));
737                         running_change = Change(Change::UNCHANGED);
738                 }
739
740                 bool close = false;
741                 odocstream::pos_type const len = os.tellp();
742
743                 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
744                      || inset->lyxCode() == Inset::MATH_CODE
745                      || inset->lyxCode() == Inset::URL_CODE)
746                     && running_font.isRightToLeft()) {
747                         if (running_font.language()->lang() == "farsi")
748                                 os << "\\beginL{}";
749                         else
750                                 os << "\\L{";
751                         close = true;
752                 }
753
754 // FIXME: Bug: we can have an empty font change here!
755 // if there has just been a font change, we are going to close it
756 // right now, which means stupid latex code like \textsf{}. AFAIK,
757 // this does not harm dvi output. A minor bug, thus (JMarc)
758                 // Some insets cannot be inside a font change command.
759                 // However, even such insets *can* be placed in \L or \R
760                 // or their equivalents (for RTL language switches), so we don't
761                 // close the language in those cases.
762                 // ArabTeX, though, cannot handle this special behavior, it seems.
763                 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
764                                            running_font.language()->lang() == "arabic_arabtex";
765                 if (open_font && inset->noFontChange()) {
766                         bool closeLanguage = arabtex ||
767                                 basefont.isRightToLeft() == running_font.isRightToLeft();
768                         unsigned int count = running_font.latexWriteEndChanges(
769                                         os, bparams, runparams,
770                                                 basefont, basefont, closeLanguage);
771                         column += count;
772                         // if any font properties were closed, update the running_font, 
773                         // making sure, however, to leave the language as it was
774                         if (count > 0) {
775                                 // FIXME: probably a better way to keep track of the old 
776                                 // language, than copying the entire font?
777                                 Font const copy_font(running_font);
778                                 basefont = owner_->getLayoutFont(bparams, outerfont);
779                                 running_font = basefont;
780                                 if (!closeLanguage)
781                                         running_font.setLanguage(copy_font.language());
782                                 // leave font open if language is still open
783                                 open_font = (running_font.language() == basefont.language());
784                                 if (closeLanguage)
785                                         runparams.local_font = &basefont;
786                         }
787                 }
788
789                 int tmp = inset->latex(buf, os, runparams);
790
791                 if (close) {
792                         if (running_font.language()->lang() == "farsi")
793                                 os << "\\endL{}";
794                         else
795                                 os << '}';
796                 }
797
798                 if (tmp) {
799                         for (int j = 0; j < tmp; ++j) {
800                                 texrow.newline();
801                         }
802                         texrow.start(owner_->id(), i + 1);
803                         column = 0;
804                 } else {
805                         column += os.tellp() - len;
806                 }
807
808                 if (lookupChange(i).type == Change::DELETED) {
809                         --runparams.inDeletedInset;
810                 }
811         }
812         break;
813
814         default:
815                 // And now for the special cases within each mode
816
817                 switch (c) {
818                 case '\\':
819                         os << "\\textbackslash{}";
820                         column += 15;
821                         break;
822
823                 case '|': case '<': case '>':
824                         // In T1 encoding, these characters exist
825                         if (lyxrc.fontenc == "T1") {
826                                 os.put(c);
827                                 //... but we should avoid ligatures
828                                 if ((c == '>' || c == '<')
829                                     && i <= size() - 2
830                                     && getChar(i + 1) == c) {
831                                         //os << "\\textcompwordmark{}";
832                                         //column += 19;
833                                         // Jean-Marc, have a look at
834                                         // this. I think this works
835                                         // equally well:
836                                         os << "\\,{}";
837                                         // Lgb
838                                         column += 3;
839                                 }
840                                 break;
841                         }
842                         // Typewriter font also has them
843                         if (running_font.family() == Font::TYPEWRITER_FAMILY) {
844                                 os.put(c);
845                                 break;
846                         }
847                         // Otherwise, we use what LaTeX
848                         // provides us.
849                         switch (c) {
850                         case '<':
851                                 os << "\\textless{}";
852                                 column += 10;
853                                 break;
854                         case '>':
855                                 os << "\\textgreater{}";
856                                 column += 13;
857                                 break;
858                         case '|':
859                                 os << "\\textbar{}";
860                                 column += 9;
861                                 break;
862                         }
863                         break;
864
865                 case '-': // "--" in Typewriter mode -> "-{}-"
866                         if (i <= size() - 2 &&
867                             getChar(i + 1) == '-' &&
868                             running_font.family() == Font::TYPEWRITER_FAMILY) {
869                                 os << "-{}";
870                                 column += 2;
871                         } else {
872                                 os << '-';
873                         }
874                         break;
875
876                 case '\"':
877                         os << "\\char`\\\"{}";
878                         column += 9;
879                         break;
880
881                 case '$': case '&':
882                 case '%': case '#': case '{':
883                 case '}': case '_':
884                         os << '\\';
885                         os.put(c);
886                         column += 1;
887                         break;
888
889                 case '~':
890                         os << "\\textasciitilde{}";
891                         column += 16;
892                         break;
893
894                 case '^':
895                         os << "\\textasciicircum{}";
896                         column += 17;
897                         break;
898
899                 case '*': case '[':
900                         // avoid being mistaken for optional arguments
901                         os << '{';
902                         os.put(c);
903                         os << '}';
904                         column += 2;
905                         break;
906
907                 case ' ':
908                         // Blanks are printed before font switching.
909                         // Sure? I am not! (try nice-latex)
910                         // I am sure it's correct. LyX might be smarter
911                         // in the future, but for now, nothing wrong is
912                         // written. (Asger)
913                         break;
914
915                 default:
916
917                         // I assume this is hack treating typewriter as verbatim
918                         // FIXME UNICODE: This can fail if c cannot be encoded
919                         // in the current encoding.
920                         if (running_font.family() == Font::TYPEWRITER_FAMILY) {
921                                 if (c != '\0') {
922                                         os.put(c);
923                                 }
924                                 break;
925                         }
926
927                         // LyX, LaTeX etc.
928
929                         // FIXME: if we have "LaTeX" with a font
930                         // change in the middle (before the 'T', then
931                         // the "TeX" part is still special cased.
932                         // Really we should only operate this on
933                         // "words" for some definition of word
934
935                         size_t pnr = 0;
936
937                         for (; pnr < phrases_nr; ++pnr) {
938                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
939                                         os << special_phrases[pnr].macro;
940                                         i += special_phrases[pnr].phrase.length() - 1;
941                                         column += special_phrases[pnr].macro.length() - 1;
942                                         break;
943                                 }
944                         }
945
946                         if (pnr == phrases_nr && c != '\0') {
947                                 Encoding const & encoding = *(runparams.encoding);
948                                 if (i < size() - 1) {
949                                         char_type next = getChar(i + 1);
950                                         if (Encodings::isCombiningChar(next)) {
951                                                 column += latexSurrogatePair(os, c, next, encoding) - 1;
952                                                 ++i;
953                                                 break;
954                                         }
955                                 }
956                                 docstring const latex = encoding.latexChar(c);
957                                 if (latex.length() > 1 &&
958                                     latex[latex.length() - 1] != '}') {
959                                         // Prevent eating of a following
960                                         // space or command corruption by
961                                         // following characters
962                                         column += latex.length() + 1;
963                                         os << latex << "{}";
964                                 } else {
965                                         column += latex.length() - 1;
966                                         os << latex;
967                                 }
968                         }
969                         break;
970                 }
971         }
972 }
973
974
975 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
976                                 Layout const & layout) const
977 {
978         BufferParams const & bparams = features.bufferParams();
979
980         // check the params.
981         if (!params.spacing().isDefault())
982                 features.require("setspace");
983
984         // then the layouts
985         features.useLayout(layout.name());
986
987         // then the fonts
988         Language const * doc_language = bparams.language;
989
990         FontList::const_iterator fcit = fontlist.begin();
991         FontList::const_iterator fend = fontlist.end();
992         for (; fcit != fend; ++fcit) {
993                 if (fcit->font().noun() == Font::ON) {
994                         LYXERR(Debug::LATEX) << "font.noun: "
995                                              << fcit->font().noun()
996                                              << endl;
997                         features.require("noun");
998                         LYXERR(Debug::LATEX) << "Noun enabled. Font: "
999                                              << to_utf8(fcit->font().stateText(0))
1000                                              << endl;
1001                 }
1002                 switch (fcit->font().color()) {
1003                 case Color::none:
1004                 case Color::inherit:
1005                 case Color::ignore:
1006                         // probably we should put here all interface colors used for
1007                         // font displaying! For now I just add this ones I know of (Jug)
1008                 case Color::latex:
1009                 case Color::note:
1010                         break;
1011                 default:
1012                         features.require("color");
1013                         LYXERR(Debug::LATEX) << "Color enabled. Font: "
1014                                              << to_utf8(fcit->font().stateText(0))
1015                                              << endl;
1016                 }
1017
1018                 Language const * language = fcit->font().language();
1019                 if (language->babel() != doc_language->babel() &&
1020                     language != ignore_language &&
1021                     language != latex_language)
1022                 {
1023                         features.useLanguage(language);
1024                         LYXERR(Debug::LATEX) << "Found language "
1025                                              << language->lang() << endl;
1026                 }
1027         }
1028
1029         if (!params.leftIndent().zero())
1030                 features.require("ParagraphLeftIndent");
1031
1032         // then the insets
1033         InsetList::const_iterator icit = owner_->insetlist.begin();
1034         InsetList::const_iterator iend = owner_->insetlist.end();
1035         for (; icit != iend; ++icit) {
1036                 if (icit->inset) {
1037                         icit->inset->validate(features);
1038                         if (layout.needprotect &&
1039                             icit->inset->lyxCode() == Inset::FOOT_CODE)
1040                                 features.require("NeedLyXFootnoteCode");
1041                 }
1042         }
1043
1044         // then the contents
1045         for (pos_type i = 0; i < size() ; ++i) {
1046                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1047                         if (!special_phrases[pnr].builtin
1048                             && isTextAt(special_phrases[pnr].phrase, i)) {
1049                                 features.require(special_phrases[pnr].phrase);
1050                                 break;
1051                         }
1052                 }
1053                 Encodings::validate(getChar(i), features);
1054         }
1055 }
1056
1057
1058 } // namespace lyx
1059
1060
1061 /////////////////////////////////////////////////////////////////////
1062 //
1063 // Paragraph
1064 //
1065 /////////////////////////////////////////////////////////////////////
1066
1067 namespace lyx {
1068
1069 Paragraph::Paragraph()
1070         : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1071 {
1072         itemdepth = 0;
1073         params().clear();
1074 }
1075
1076
1077 Paragraph::Paragraph(Paragraph const & par)
1078         : itemdepth(par.itemdepth), insetlist(par.insetlist),
1079         layout_(par.layout_),
1080         text_(par.text_), begin_of_body_(par.begin_of_body_),
1081         pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1082 {
1083         //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1084         InsetList::iterator it = insetlist.begin();
1085         InsetList::iterator end = insetlist.end();
1086         for (; it != end; ++it)
1087                 it->inset = it->inset->clone().release();
1088 }
1089
1090
1091 Paragraph & Paragraph::operator=(Paragraph const & par)
1092 {
1093         // needed as we will destroy the pimpl_ before copying it
1094         if (&par != this) {
1095                 itemdepth = par.itemdepth;
1096
1097                 insetlist = par.insetlist;
1098                 InsetList::iterator it = insetlist.begin();
1099                 InsetList::iterator end = insetlist.end();
1100                 for (; it != end; ++it)
1101                         it->inset = it->inset->clone().release();
1102
1103                 layout_ = par.layout();
1104                 text_ = par.text_;
1105                 begin_of_body_ = par.begin_of_body_;
1106
1107                 delete pimpl_;
1108                 pimpl_ = new Pimpl(*par.pimpl_, this);
1109         }
1110         return *this;
1111 }
1112
1113
1114 Paragraph::~Paragraph()
1115 {
1116         delete pimpl_;
1117         //
1118         //lyxerr << "Paragraph::paragraph_id = "
1119         //       << Paragraph::paragraph_id << endl;
1120 }
1121
1122
1123 void Paragraph::write(Buffer const & buf, ostream & os,
1124                           BufferParams const & bparams,
1125                           depth_type & dth) const
1126 {
1127         // The beginning or end of a deeper (i.e. nested) area?
1128         if (dth != params().depth()) {
1129                 if (params().depth() > dth) {
1130                         while (params().depth() > dth) {
1131                                 os << "\n\\begin_deeper";
1132                                 ++dth;
1133                         }
1134                 } else {
1135                         while (params().depth() < dth) {
1136                                 os << "\n\\end_deeper";
1137                                 --dth;
1138                         }
1139                 }
1140         }
1141
1142         // First write the layout
1143         os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1144
1145         params().write(os);
1146
1147         Font font1(Font::ALL_INHERIT, bparams.language);
1148
1149         Change running_change = Change(Change::UNCHANGED);
1150
1151         int column = 0;
1152         for (pos_type i = 0; i <= size(); ++i) {
1153
1154                 Change change = pimpl_->lookupChange(i);
1155                 Changes::lyxMarkChange(os, column, running_change, change);
1156                 running_change = change;
1157
1158                 if (i == size())
1159                         break;
1160
1161                 // Write font changes
1162                 Font font2 = getFontSettings(bparams, i);
1163                 if (font2 != font1) {
1164                         font2.lyxWriteChanges(font1, os);
1165                         column = 0;
1166                         font1 = font2;
1167                 }
1168
1169                 value_type const c = getChar(i);
1170                 switch (c) {
1171                 case META_INSET:
1172                 {
1173                         Inset const * inset = getInset(i);
1174                         if (inset)
1175                                 if (inset->directWrite()) {
1176                                         // international char, let it write
1177                                         // code directly so it's shorter in
1178                                         // the file
1179                                         inset->write(buf, os);
1180                                 } else {
1181                                         if (i)
1182                                                 os << '\n';
1183                                         os << "\\begin_inset ";
1184                                         inset->write(buf, os);
1185                                         os << "\n\\end_inset\n\n";
1186                                         column = 0;
1187                                 }
1188                 }
1189                 break;
1190                 case '\\':
1191                         os << "\n\\backslash\n";
1192                         column = 0;
1193                         break;
1194                 case '.':
1195                         if (i + 1 < size() && getChar(i + 1) == ' ') {
1196                                 os << ".\n";
1197                                 column = 0;
1198                         } else
1199                                 os << '.';
1200                         break;
1201                 default:
1202                         if ((column > 70 && c == ' ')
1203                             || column > 79) {
1204                                 os << '\n';
1205                                 column = 0;
1206                         }
1207                         // this check is to amend a bug. LyX sometimes
1208                         // inserts '\0' this could cause problems.
1209                         if (c != '\0') {
1210                                 std::vector<char> tmp = ucs4_to_utf8(c);
1211                                 tmp.push_back('\0');
1212                                 os << &tmp[0];
1213                         } else
1214                                 lyxerr << "ERROR (Paragraph::writeFile):"
1215                                         " NULL char in structure." << endl;
1216                         ++column;
1217                         break;
1218                 }
1219         }
1220
1221         os << "\n\\end_layout\n";
1222 }
1223
1224
1225 void Paragraph::validate(LaTeXFeatures & features) const
1226 {
1227         pimpl_->validate(features, *layout());
1228 }
1229
1230
1231 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1232 {
1233         return pimpl_->eraseChar(pos, trackChanges);
1234 }
1235
1236
1237 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1238 {
1239         return pimpl_->eraseChars(start, end, trackChanges);
1240 }
1241
1242
1243 void Paragraph::insert(pos_type start, docstring const & str,
1244                        Font const & font, Change const & change)
1245 {
1246         for (size_t i = 0, n = str.size(); i != n ; ++i)
1247                 insertChar(start + i, str[i], font, change);
1248 }
1249
1250
1251 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1252                            bool trackChanges)
1253 {
1254         pimpl_->insertChar(pos, c, Change(trackChanges ?
1255                            Change::INSERTED : Change::UNCHANGED));
1256 }
1257
1258
1259 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1260                            Font const & font, bool trackChanges)
1261 {
1262         pimpl_->insertChar(pos, c, Change(trackChanges ?
1263                            Change::INSERTED : Change::UNCHANGED));
1264         setFont(pos, font);
1265 }
1266
1267
1268 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1269                            Font const & font, Change const & change)
1270 {
1271         pimpl_->insertChar(pos, c, change);
1272         setFont(pos, font);
1273 }
1274
1275
1276 void Paragraph::insertInset(pos_type pos, Inset * inset,
1277                             Change const & change)
1278 {
1279         pimpl_->insertInset(pos, inset, change);
1280 }
1281
1282
1283 void Paragraph::insertInset(pos_type pos, Inset * inset,
1284                             Font const & font, Change const & change)
1285 {
1286         pimpl_->insertInset(pos, inset, change);
1287         // Set the font/language of the inset...
1288         setFont(pos, font);
1289         // ... as well as the font/language of the text inside the inset
1290         // FIXME: This is far from perfect. It basically overrides work being done
1291         // in the InsetText constructor. Also, it doesn't work for Tables 
1292         // (precisely because each cell's font/language is set in the Table's 
1293         // constructor, so by now it's too late). The long-term solution should
1294         // be moving current_font into Cursor, and getting rid of all this...
1295         // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1296         if (inset->asTextInset()) {
1297                 inset->asTextInset()->text_.current_font = font;
1298                 inset->asTextInset()->text_.real_current_font = font;
1299         }
1300 }
1301
1302
1303 bool Paragraph::insetAllowed(Inset_code code)
1304 {
1305         return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1306 }
1307
1308
1309 // Gets uninstantiated font setting at position.
1310 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1311                                          pos_type pos) const
1312 {
1313         if (pos > size()) {
1314                 lyxerr << " pos: " << pos << " size: " << size() << endl;
1315                 BOOST_ASSERT(pos <= size());
1316         }
1317
1318         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1319         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1320         for (; cit != end; ++cit)
1321                 if (cit->pos() >= pos)
1322                         break;
1323
1324         if (cit != end)
1325                 return cit->font();
1326
1327         if (pos == size() && !empty())
1328                 return getFontSettings(bparams, pos - 1);
1329
1330         return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1331 }
1332
1333
1334 FontSpan Paragraph::fontSpan(pos_type pos) const
1335 {
1336         BOOST_ASSERT(pos <= size());
1337         pos_type start = 0;
1338
1339         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1340         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1341         for (; cit != end; ++cit) {
1342                 if (cit->pos() >= pos) {
1343                         if (pos >= beginOfBody())
1344                                 return FontSpan(std::max(start, beginOfBody()),
1345                                                 cit->pos());
1346                         else
1347                                 return FontSpan(start,
1348                                                 std::min(beginOfBody() - 1,
1349                                                          cit->pos()));
1350                 }
1351                 start = cit->pos() + 1;
1352         }
1353
1354         // This should not happen, but if so, we take no chances.
1355         //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1356         //      << endl;
1357         return FontSpan(pos, pos);
1358 }
1359
1360
1361 // Gets uninstantiated font setting at position 0
1362 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1363 {
1364         if (!empty() && !pimpl_->fontlist.empty())
1365                 return pimpl_->fontlist[0].font();
1366
1367         return Font(Font::ALL_INHERIT, bparams.language);
1368 }
1369
1370
1371 // Gets the fully instantiated font at a given position in a paragraph
1372 // This is basically the same function as Text::GetFont() in text2.cpp.
1373 // The difference is that this one is used for generating the LaTeX file,
1374 // and thus cosmetic "improvements" are disallowed: This has to deliver
1375 // the true picture of the buffer. (Asger)
1376 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1377                                  Font const & outerfont) const
1378 {
1379         BOOST_ASSERT(pos >= 0);
1380
1381         Layout_ptr const & lout = layout();
1382
1383         pos_type const body_pos = beginOfBody();
1384
1385         Font layoutfont;
1386         if (pos < body_pos)
1387                 layoutfont = lout->labelfont;
1388         else
1389                 layoutfont = lout->font;
1390
1391         Font font = getFontSettings(bparams, pos);
1392         font.realize(layoutfont);
1393         font.realize(outerfont);
1394         font.realize(bparams.getFont());
1395
1396         return font;
1397 }
1398
1399
1400 Font const Paragraph::getLabelFont
1401         (BufferParams const & bparams, Font const & outerfont) const
1402 {
1403         Font tmpfont = layout()->labelfont;
1404         tmpfont.setLanguage(getParLanguage(bparams));
1405         tmpfont.realize(outerfont);
1406         tmpfont.realize(bparams.getFont());
1407         return tmpfont;
1408 }
1409
1410
1411 Font const Paragraph::getLayoutFont
1412         (BufferParams const & bparams, Font const & outerfont) const
1413 {
1414         Font tmpfont = layout()->font;
1415         tmpfont.setLanguage(getParLanguage(bparams));
1416         tmpfont.realize(outerfont);
1417         tmpfont.realize(bparams.getFont());
1418         return tmpfont;
1419 }
1420
1421
1422 /// Returns the height of the highest font in range
1423 Font_size Paragraph::highestFontInRange
1424         (pos_type startpos, pos_type endpos, Font_size def_size) const
1425 {
1426         if (pimpl_->fontlist.empty())
1427                 return def_size;
1428
1429         Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1430         Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1431         for (; end_it != end; ++end_it) {
1432                 if (end_it->pos() >= endpos)
1433                         break;
1434         }
1435
1436         if (end_it != end)
1437                 ++end_it;
1438
1439         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1440         for (; cit != end; ++cit) {
1441                 if (cit->pos() >= startpos)
1442                         break;
1443         }
1444
1445         Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1446         for (; cit != end_it; ++cit) {
1447                 Font::FONT_SIZE size = cit->font().size();
1448                 if (size == Font::INHERIT_SIZE)
1449                         size = def_size;
1450                 if (size > maxsize && size <= Font::SIZE_HUGER)
1451                         maxsize = size;
1452         }
1453         return maxsize;
1454 }
1455
1456
1457 Paragraph::value_type
1458 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1459 {
1460         value_type c = getChar(pos);
1461         if (!lyxrc.rtl_support)
1462                 return c;
1463
1464         value_type uc = c;
1465         switch (c) {
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         case '<':
1485                 uc = '>';
1486                 break;
1487         case '>':
1488                 uc = '<';
1489                 break;
1490         }
1491         if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1492                 return uc;
1493         else
1494                 return c;
1495 }
1496
1497
1498 void Paragraph::setFont(pos_type pos, Font const & font)
1499 {
1500         BOOST_ASSERT(pos <= size());
1501
1502         // First, reduce font against layout/label font
1503         // Update: The setCharFont() routine in text2.cpp already
1504         // reduces font, so we don't need to do that here. (Asger)
1505         // No need to simplify this because it will disappear
1506         // in a new kernel. (Asger)
1507         // Next search font table
1508
1509         Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1510         Pimpl::FontList::iterator it = beg;
1511         Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1512         for (; it != endit; ++it) {
1513                 if (it->pos() >= pos)
1514                         break;
1515         }
1516         size_t const i = distance(beg, it);
1517         bool notfound = (it == endit);
1518
1519         if (!notfound && pimpl_->fontlist[i].font() == font)
1520                 return;
1521
1522         bool begin = pos == 0 || notfound ||
1523                 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1524         // Is position pos is a beginning of a font block?
1525         bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1526         // Is position pos is the end of a font block?
1527         if (begin && end) { // A single char block
1528                 if (i + 1 < pimpl_->fontlist.size() &&
1529                     pimpl_->fontlist[i + 1].font() == font) {
1530                         // Merge the singleton block with the next block
1531                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1532                         if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1533                                 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1534                 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1535                         // Merge the singleton block with the previous block
1536                         pimpl_->fontlist[i - 1].pos(pos);
1537                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1538                 } else
1539                         pimpl_->fontlist[i].font(font);
1540         } else if (begin) {
1541                 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1542                         pimpl_->fontlist[i - 1].pos(pos);
1543                 else
1544                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1545                                         Pimpl::FontTable(pos, font));
1546         } else if (end) {
1547                 pimpl_->fontlist[i].pos(pos - 1);
1548                 if (!(i + 1 < pimpl_->fontlist.size() &&
1549                       pimpl_->fontlist[i + 1].font() == font))
1550                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1551                                         Pimpl::FontTable(pos, font));
1552         } else { // The general case. The block is splitted into 3 blocks
1553                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1554                                 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1555                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1556                                 Pimpl::FontTable(pos, font));
1557         }
1558 }
1559
1560
1561 void Paragraph::makeSameLayout(Paragraph const & par)
1562 {
1563         layout(par.layout());
1564         // move to pimpl?
1565         params() = par.params();
1566 }
1567
1568
1569 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1570 {
1571         if (isFreeSpacing())
1572                 return false;
1573
1574         int pos = 0;
1575         int count = 0;
1576
1577         while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1578                 if (eraseChar(pos, trackChanges))
1579                         ++count;
1580                 else
1581                         ++pos;
1582         }
1583
1584         return count > 0 || pos > 0;
1585 }
1586
1587
1588 bool Paragraph::hasSameLayout(Paragraph const & par) const
1589 {
1590         return par.layout() == layout() && params().sameLayout(par.params());
1591 }
1592
1593
1594 depth_type Paragraph::getDepth() const
1595 {
1596         return params().depth();
1597 }
1598
1599
1600 depth_type Paragraph::getMaxDepthAfter() const
1601 {
1602         if (layout()->isEnvironment())
1603                 return params().depth() + 1;
1604         else
1605                 return params().depth();
1606 }
1607
1608
1609 char Paragraph::getAlign() const
1610 {
1611         if (params().align() == LYX_ALIGN_LAYOUT)
1612                 return layout()->align;
1613         else
1614                 return params().align();
1615 }
1616
1617
1618 docstring const & Paragraph::getLabelstring() const
1619 {
1620         return params().labelString();
1621 }
1622
1623
1624 // the next two functions are for the manual labels
1625 docstring const Paragraph::getLabelWidthString() const
1626 {
1627         if (!params().labelWidthString().empty())
1628                 return params().labelWidthString();
1629         else
1630                 return _("Senseless with this layout!");
1631 }
1632
1633
1634 void Paragraph::setLabelWidthString(docstring const & s)
1635 {
1636         params().labelWidthString(s);
1637 }
1638
1639
1640 docstring const Paragraph::translateIfPossible(docstring const & s,
1641                 BufferParams const & bparams) const
1642 {
1643         if (!support::isAscii(s) || s.empty()) {
1644                 // This must be a user defined layout. We cannot translate
1645                 // this, since gettext accepts only ascii keys.
1646                 return s;
1647         }
1648         // Probably standard layout, try to translate
1649         Messages & m = getMessages(getParLanguage(bparams)->code());
1650         return m.get(to_ascii(s));
1651 }
1652
1653
1654 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1655                 BufferParams const & bparams, bool process_appendix) const
1656 {
1657         TextClass const & tclass = bparams.getTextClass();
1658
1659         docstring fmt;
1660         if (process_appendix && params().appendix())
1661                 fmt = translateIfPossible(layout->labelstring_appendix(),
1662                         bparams);
1663         else
1664                 fmt = translateIfPossible(layout->labelstring(), bparams);
1665
1666         // handle 'inherited level parts' in 'fmt',
1667         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
1668         size_t const i = fmt.find('@', 0);
1669         if (i != docstring::npos) {
1670                 size_t const j = fmt.find('@', i + 1);
1671                 if (j != docstring::npos) {
1672                         docstring parent(fmt, i + 1, j - i - 1);
1673                         docstring label = expandLabel(tclass[parent], bparams);
1674                         fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1675                 }
1676         }
1677
1678         return tclass.counters().counterLabel(fmt);
1679 }
1680
1681
1682 void Paragraph::applyLayout(Layout_ptr 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         Layout_ptr 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         Layout_ptr 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 Layout_ptr const & Paragraph::layout() const
2531 {
2532         return layout_;
2533 }
2534
2535
2536 void Paragraph::layout(Layout_ptr 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 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2627 {
2628         if (!isHfill(pos))
2629                 return false;
2630
2631         BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2632
2633         // expand at the end of a row only if there is another hfill on the same row
2634         if (pos == row.endpos() - 1) {
2635                 for (pos_type i = row.pos(); i < pos; i++) {
2636                         if (isHfill(i))
2637                                 return true;
2638                 }
2639                 return false;
2640         }
2641
2642         // expand at the beginning of a row only if it is the first row of a paragraph
2643         if (pos == row.pos()) {
2644                 return pos == 0;
2645         }
2646
2647         // do not expand in some labels
2648         if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2649                 return false;
2650
2651         // if there is anything between the first char of the row and
2652         // the specified position that is neither a newline nor an hfill,
2653         // the hfill will be expanded, otherwise it won't
2654         for (pos_type i = row.pos(); i < pos; i++) {
2655                 if (!isNewline(i) && !isHfill(i))
2656                         return true;
2657         }
2658         return false;
2659 }
2660
2661
2662 int Paragraph::checkBiblio(bool track_changes)
2663 {
2664         //FIXME From JS:
2665         //This is getting more and more a mess. ...We really should clean
2666         //up this bibitem issue for 1.6. See also bug 2743.
2667
2668         // Add bibitem insets if necessary
2669         if (layout()->labeltype != LABEL_BIBLIO)
2670                 return 0;
2671
2672         bool hasbibitem = !insetlist.empty()
2673                 // Insist on it being in pos 0
2674                 && getChar(0) == Paragraph::META_INSET
2675                 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2676
2677         docstring oldkey;
2678         docstring oldlabel;
2679
2680         // remove a bibitem in pos != 0
2681         // restore it later in pos 0 if necessary
2682         // (e.g. if a user inserts contents _before_ the item)
2683         // we're assuming there's only one of these, which there
2684         // should be.
2685         int erasedInsetPosition = -1;
2686         InsetList::iterator it = insetlist.begin();
2687         InsetList::iterator end = insetlist.end();
2688         for (; it != end; ++it)
2689                 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2690                     && it->pos > 0) {
2691                         InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2692                         oldkey = olditem->getParam("key");
2693                         oldlabel = olditem->getParam("label");
2694                         erasedInsetPosition = it->pos;
2695                         eraseChar(erasedInsetPosition, track_changes);
2696                         break;
2697         }
2698
2699         //There was an InsetBibitem at the beginning, and we didn't
2700         //have to erase one.
2701         if (hasbibitem && erasedInsetPosition < 0)
2702                         return 0;
2703
2704         //There was an InsetBibitem at the beginning and we did have to
2705         //erase one. So we give its properties to the beginning inset.
2706         if (hasbibitem) {
2707                 InsetBibitem * inset =
2708                         static_cast<InsetBibitem *>(insetlist.begin()->inset);
2709                 if (!oldkey.empty())
2710                         inset->setParam("key", oldkey);
2711                 inset->setParam("label", oldlabel);
2712                 return -erasedInsetPosition;
2713         }
2714
2715         //There was no inset at the beginning, so we need to create one with
2716         //the key and label of the one we erased.
2717         InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2718         // restore values of previously deleted item in this par.
2719         if (!oldkey.empty())
2720                 inset->setParam("key", oldkey);
2721         inset->setParam("label", oldlabel);
2722         insertInset(0, static_cast<Inset *>(inset),
2723                     Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2724
2725         return 1;
2726 }
2727
2728
2729 void Paragraph::checkAuthors(AuthorList const & authorList)
2730 {
2731         pimpl_->changes_.checkAuthors(authorList);
2732 }
2733
2734 } // namespace lyx