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