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