]> git.lyx.org Git - features.git/blob - src/paragraph.C
un-pimpl Paragraph::(Pimpl::)text for performance reasons
[features.git] / src / paragraph.C
1 /**
2  * \file paragraph.C
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 #include "paragraph_pimpl.h"
22
23 #include "buffer.h"
24 #include "bufferparams.h"
25 #include "encoding.h"
26 #include "debug.h"
27 #include "gettext.h"
28 #include "language.h"
29 #include "latexrunparams.h"
30 #include "lyxrc.h"
31 #include "lyxrow.h"
32 #include "texrow.h"
33 #include "vspace.h"
34
35 #include "insets/insetbibitem.h"
36 #include "insets/insetoptarg.h"
37
38 #include "support/lstrings.h"
39 #include "support/textutils.h"
40 #include "support/std_sstream.h"
41
42 using lyx::pos_type;
43
44 using lyx::support::contains;
45 using lyx::support::subst;
46
47 using std::endl;
48
49 using std::ostream;
50 using std::ostringstream;
51
52
53 Paragraph::Paragraph()
54         : y(0), pimpl_(new Paragraph::Pimpl(this))
55 {
56         enumdepth = 0;
57         itemdepth = 0;
58         params().clear();
59 }
60
61
62 Paragraph::Paragraph(Paragraph const & lp)
63         : y(0), text_(lp.text_), pimpl_(new Paragraph::Pimpl(*lp.pimpl_, this))
64 {
65         enumdepth = 0;
66         itemdepth = 0;
67         // this is because of the dummy layout of the paragraphs that
68         // follow footnotes
69         layout_ = lp.layout();
70
71         // copy everything behind the break-position to the new paragraph
72         insetlist = lp.insetlist;
73         InsetList::iterator it = insetlist.begin();
74         InsetList::iterator end = insetlist.end();
75         for (; it != end; ++it) {
76                 // currently we hold Inset*, not InsetBase*
77                 it->inset = static_cast<InsetOld*>(it->inset->clone().release());
78         }
79 }
80
81
82 void Paragraph::operator=(Paragraph const & lp)
83 {
84         // needed as we will destroy the pimpl_ before copying it
85         if (&lp != this)
86                 return;
87
88         lyxerr << "Paragraph::operator=()" << endl;
89
90         text_ = lp.text_;
91
92         delete pimpl_;
93         pimpl_ = new Pimpl(*lp.pimpl_, this);
94
95         enumdepth = lp.enumdepth;
96         itemdepth = lp.itemdepth;
97         // this is because of the dummy layout of the paragraphs that
98         // follow footnotes
99         layout_ = lp.layout();
100
101         // copy everything behind the break-position to the new paragraph
102         insetlist = lp.insetlist;
103         InsetList::iterator it = insetlist.begin();
104         InsetList::iterator end = insetlist.end();
105         for (; it != end; ++it) {
106                 it->inset = static_cast<InsetOld*>(it->inset->clone().release());
107         }
108 }
109
110 // the destructor removes the new paragraph from the list
111 Paragraph::~Paragraph()
112 {
113         delete pimpl_;
114         //
115         //lyxerr << "Paragraph::paragraph_id = "
116         //       << Paragraph::paragraph_id << endl;
117 }
118
119
120 void Paragraph::write(Buffer const & buf, ostream & os,
121                           BufferParams const & bparams,
122                           depth_type & dth) const
123 {
124         // The beginning or end of a deeper (i.e. nested) area?
125         if (dth != params().depth()) {
126                 if (params().depth() > dth) {
127                         while (params().depth() > dth) {
128                                 os << "\n\\begin_deeper ";
129                                 ++dth;
130                         }
131                 } else {
132                         while (params().depth() < dth) {
133                                 os << "\n\\end_deeper ";
134                                 --dth;
135                         }
136                 }
137         }
138
139         // First write the layout
140         os << "\n\\begin_layout " << layout()->name() << '\n';
141
142         params().write(os);
143
144         LyXFont font1(LyXFont::ALL_INHERIT, bparams.language);
145
146         Change running_change = Change(Change::UNCHANGED);
147         lyx::time_type const curtime(lyx::current_time());
148
149         int column = 0;
150         for (pos_type i = 0; i < size(); ++i) {
151                 if (!i) {
152                         os << '\n';
153                         column = 0;
154                 }
155
156                 Change change = pimpl_->lookupChangeFull(i);
157                 Changes::lyxMarkChange(os, column, curtime, running_change, change);
158                 running_change = change;
159
160                 // Write font changes
161                 LyXFont font2 = getFontSettings(bparams, i);
162                 if (font2 != font1) {
163                         font2.lyxWriteChanges(font1, os);
164                         column = 0;
165                         font1 = font2;
166                 }
167
168                 value_type const c = getChar(i);
169                 switch (c) {
170                 case META_INSET:
171                 {
172                         InsetOld const * inset = getInset(i);
173                         if (inset)
174                                 if (inset->directWrite()) {
175                                         // international char, let it write
176                                         // code directly so it's shorter in
177                                         // the file
178                                         inset->write(buf, os);
179                                 } else {
180                                         os << "\n\\begin_inset ";
181                                         inset->write(buf, os);
182                                         os << "\n\\end_inset \n\n";
183                                         column = 0;
184                                 }
185                 }
186                 break;
187                 case '\\':
188                         os << "\n\\backslash \n";
189                         column = 0;
190                         break;
191                 case '.':
192                         if (i + 1 < size() && getChar(i + 1) == ' ') {
193                                 os << ".\n";
194                                 column = 0;
195                         } else
196                                 os << '.';
197                         break;
198                 default:
199                         if ((column > 70 && c == ' ')
200                             || column > 79) {
201                                 os << '\n';
202                                 column = 0;
203                         }
204                         // this check is to amend a bug. LyX sometimes
205                         // inserts '\0' this could cause problems.
206                         if (c != '\0')
207                                 os << c;
208                         else
209                                 lyxerr << "ERROR (Paragraph::writeFile):"
210                                         " NULL char in structure." << endl;
211                         ++column;
212                         break;
213                 }
214         }
215
216         // to make reading work properly
217         if (!size()) {
218                 running_change = pimpl_->lookupChange(0);
219                 Changes::lyxMarkChange(os, column, curtime,
220                         Change(Change::UNCHANGED), running_change);
221         }
222         Changes::lyxMarkChange(os, column, curtime,
223                 running_change, Change(Change::UNCHANGED));
224
225         os << "\n\\end_layout\n";
226 }
227
228
229 void Paragraph::validate(LaTeXFeatures & features) const
230 {
231         pimpl_->validate(features, *layout());
232 }
233
234
235 void Paragraph::eraseIntern(lyx::pos_type pos)
236 {
237         pimpl_->eraseIntern(pos);
238 }
239
240
241 bool Paragraph::erase(pos_type pos)
242 {
243         return pimpl_->erase(pos);
244 }
245
246
247 int Paragraph::erase(pos_type start, pos_type end)
248 {
249         return pimpl_->erase(start, end);
250 }
251
252
253 bool Paragraph::checkInsertChar(LyXFont & font)
254 {
255         if (pimpl_->inset_owner)
256                 return pimpl_->inset_owner->checkInsertChar(font);
257         return true;
258 }
259
260
261 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c)
262 {
263         insertChar(pos, c, LyXFont(LyXFont::ALL_INHERIT));
264 }
265
266
267 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
268                            LyXFont const & font, Change change)
269 {
270         pimpl_->insertChar(pos, c, font, change);
271 }
272
273
274 void Paragraph::insertInset(pos_type pos, InsetOld * inset)
275 {
276         insertInset(pos, inset, LyXFont(LyXFont::ALL_INHERIT));
277 }
278
279
280 void Paragraph::insertInset(pos_type pos, InsetOld * inset,
281         LyXFont const & font, Change change)
282 {
283         pimpl_->insertInset(pos, inset, font, change);
284 }
285
286
287 bool Paragraph::insetAllowed(InsetOld::Code code)
288 {
289         //lyxerr << "Paragraph::InsertInsetAllowed" << endl;
290         if (pimpl_->inset_owner)
291                 return pimpl_->inset_owner->insetAllowed(code);
292         return true;
293 }
294
295
296 InsetOld * Paragraph::getInset(pos_type pos)
297 {
298         BOOST_ASSERT(pos < size());
299         return insetlist.get(pos);
300 }
301
302
303 InsetOld const * Paragraph::getInset(pos_type pos) const
304 {
305         BOOST_ASSERT(pos < size());
306         return insetlist.get(pos);
307 }
308
309
310 // Gets uninstantiated font setting at position.
311 LyXFont const Paragraph::getFontSettings(BufferParams const & bparams,
312                                          pos_type pos) const
313 {
314         BOOST_ASSERT(pos <= size());
315
316         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
317         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
318         for (; cit != end; ++cit)
319                 if (cit->pos() >= pos)
320                         break;
321
322         if (cit != end)
323                 return cit->font();
324
325         if (pos == size() && !empty())
326                 return getFontSettings(bparams, pos - 1);
327
328         return LyXFont(LyXFont::ALL_INHERIT, getParLanguage(bparams));
329 }
330
331
332 lyx::pos_type Paragraph::getEndPosOfFontSpan(lyx::pos_type pos) const
333 {
334         BOOST_ASSERT(pos <= size());
335
336         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
337         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
338         for (; cit != end; ++cit)
339                 if (cit->pos() >= pos)
340                         return cit->pos();
341
342         // This should not happen, but if so, we take no chances.
343         //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
344         //      << endl;
345         return pos;
346 }
347
348
349 // Gets uninstantiated font setting at position 0
350 LyXFont const Paragraph::getFirstFontSettings() const
351 {
352         if (!empty() && !pimpl_->fontlist.empty())
353                 return pimpl_->fontlist[0].font();
354
355         return LyXFont(LyXFont::ALL_INHERIT);
356 }
357
358
359 // Gets the fully instantiated font at a given position in a paragraph
360 // This is basically the same function as LyXText::GetFont() in text2.C.
361 // The difference is that this one is used for generating the LaTeX file,
362 // and thus cosmetic "improvements" are disallowed: This has to deliver
363 // the true picture of the buffer. (Asger)
364 LyXFont const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
365                                  LyXFont const & outerfont) const
366 {
367         BOOST_ASSERT(pos >= 0);
368
369         LyXLayout_ptr const & lout = layout();
370
371         pos_type const body_pos = beginningOfBody();
372
373         LyXFont layoutfont;
374         if (pos < body_pos)
375                 layoutfont = lout->labelfont;
376         else
377                 layoutfont = lout->font;
378
379         LyXFont tmpfont = getFontSettings(bparams, pos);
380         tmpfont.realize(layoutfont);
381         tmpfont.realize(outerfont);
382         tmpfont.realize(bparams.getLyXTextClass().defaultfont());
383
384         return tmpfont;
385 }
386
387
388 LyXFont const Paragraph::getLabelFont(BufferParams const & bparams,
389                                       LyXFont const & outerfont) const
390 {
391         LyXFont tmpfont = layout()->labelfont;
392         tmpfont.setLanguage(getParLanguage(bparams));
393         tmpfont.realize(outerfont);
394         tmpfont.realize(bparams.getLyXTextClass().defaultfont());
395         return tmpfont;
396 }
397
398
399 LyXFont const Paragraph::getLayoutFont(BufferParams const & bparams,
400                                        LyXFont const & outerfont) const
401 {
402         LyXFont tmpfont = layout()->font;
403         tmpfont.setLanguage(getParLanguage(bparams));
404         tmpfont.realize(outerfont);
405         tmpfont.realize(bparams.getLyXTextClass().defaultfont());
406         return tmpfont;
407 }
408
409
410 /// Returns the height of the highest font in range
411 LyXFont_size
412 Paragraph::highestFontInRange(pos_type startpos, pos_type endpos,
413                               LyXFont_size def_size) const
414 {
415         if (pimpl_->fontlist.empty())
416                 return def_size;
417
418         Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
419         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
420         for (; end_it != end; ++end_it) {
421                 if (end_it->pos() >= endpos)
422                         break;
423         }
424
425         if (end_it != end)
426                 ++end_it;
427
428         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
429         for (; cit != end; ++cit) {
430                 if (cit->pos() >= startpos)
431                         break;
432         }
433
434         LyXFont::FONT_SIZE maxsize = LyXFont::SIZE_TINY;
435         for (; cit != end_it; ++cit) {
436                 LyXFont::FONT_SIZE size = cit->font().size();
437                 if (size == LyXFont::INHERIT_SIZE)
438                         size = def_size;
439                 if (size > maxsize && size <= LyXFont::SIZE_HUGER)
440                         maxsize = size;
441         }
442         return maxsize;
443 }
444
445
446 Paragraph::value_type
447 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
448 {
449         value_type c = getChar(pos);
450         if (!lyxrc.rtl_support)
451                 return c;
452
453         value_type uc = c;
454         switch (c) {
455         case '(':
456                 uc = ')';
457                 break;
458         case ')':
459                 uc = '(';
460                 break;
461         case '[':
462                 uc = ']';
463                 break;
464         case ']':
465                 uc = '[';
466                 break;
467         case '{':
468                 uc = '}';
469                 break;
470         case '}':
471                 uc = '{';
472                 break;
473         case '<':
474                 uc = '>';
475                 break;
476         case '>':
477                 uc = '<';
478                 break;
479         }
480         if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
481                 return uc;
482         else
483                 return c;
484 }
485
486
487 void Paragraph::setFont(pos_type pos, LyXFont const & font)
488 {
489         BOOST_ASSERT(pos <= size());
490
491         // First, reduce font against layout/label font
492         // Update: The SetCharFont() routine in text2.C already
493         // reduces font, so we don't need to do that here. (Asger)
494         // No need to simplify this because it will disappear
495         // in a new kernel. (Asger)
496         // Next search font table
497
498         Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
499         Pimpl::FontList::iterator it = beg;
500         Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
501         for (; it != endit; ++it) {
502                 if (it->pos() >= pos)
503                         break;
504         }
505         unsigned int i = std::distance(beg, it);
506         bool notfound = (it == endit);
507
508         if (!notfound && pimpl_->fontlist[i].font() == font)
509                 return;
510
511         bool begin = pos == 0 || notfound ||
512                 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
513         // Is position pos is a beginning of a font block?
514         bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
515         // Is position pos is the end of a font block?
516         if (begin && end) { // A single char block
517                 if (i + 1 < pimpl_->fontlist.size() &&
518                     pimpl_->fontlist[i + 1].font() == font) {
519                         // Merge the singleton block with the next block
520                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
521                         if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
522                                 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
523                 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
524                         // Merge the singleton block with the previous block
525                         pimpl_->fontlist[i - 1].pos(pos);
526                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
527                 } else
528                         pimpl_->fontlist[i].font(font);
529         } else if (begin) {
530                 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
531                         pimpl_->fontlist[i - 1].pos(pos);
532                 else
533                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
534                                         Pimpl::FontTable(pos, font));
535         } else if (end) {
536                 pimpl_->fontlist[i].pos(pos - 1);
537                 if (!(i + 1 < pimpl_->fontlist.size() &&
538                       pimpl_->fontlist[i + 1].font() == font))
539                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
540                                         Pimpl::FontTable(pos, font));
541         } else { // The general case. The block is splitted into 3 blocks
542                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
543                                 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
544                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
545                                 Pimpl::FontTable(pos, font));
546         }
547 }
548
549
550 void Paragraph::makeSameLayout(Paragraph const & par)
551 {
552         layout(par.layout());
553         // move to pimpl?
554         params() = par.params();
555 }
556
557
558 int Paragraph::stripLeadingSpaces()
559 {
560         if (isFreeSpacing())
561                 return 0;
562
563         int i = 0;
564         while (!empty() && (isNewline(0) || isLineSeparator(0))) {
565                 pimpl_->eraseIntern(0);
566                 ++i;
567         }
568
569         return i;
570 }
571
572
573 bool Paragraph::hasSameLayout(Paragraph const & par) const
574 {
575         return
576                 par.layout() == layout() &&
577                 params().sameLayout(par.params());
578 }
579
580
581 Paragraph::depth_type Paragraph::getDepth() const
582 {
583         return params().depth();
584 }
585
586
587 Paragraph::depth_type Paragraph::getMaxDepthAfter() const
588 {
589         if (layout()->isEnvironment())
590                 return params().depth() + 1;
591         else
592                 return params().depth();
593 }
594
595
596 char Paragraph::getAlign() const
597 {
598         return params().align();
599 }
600
601
602 string const & Paragraph::getLabelstring() const
603 {
604         return params().labelString();
605 }
606
607
608 // the next two functions are for the manual labels
609 string const Paragraph::getLabelWidthString() const
610 {
611         if (!params().labelWidthString().empty())
612                 return params().labelWidthString();
613         else
614                 return _("Senseless with this layout!");
615 }
616
617
618 void Paragraph::setLabelWidthString(string const & s)
619 {
620         params().labelWidthString(s);
621 }
622
623
624 void Paragraph::applyLayout(LyXLayout_ptr const & new_layout)
625 {
626         layout(new_layout);
627         params().labelWidthString(string());
628         params().align(LYX_ALIGN_LAYOUT);
629         params().spaceTop(VSpace(VSpace::NONE));
630         params().spaceBottom(VSpace(VSpace::NONE));
631         params().spacing(Spacing(Spacing::Default));
632 }
633
634
635 int Paragraph::beginningOfBody() const
636 {
637         if (layout()->labeltype != LABEL_MANUAL)
638                 return 0;
639
640         // Unroll the first two cycles of the loop
641         // and remember the previous character to
642         // remove unnecessary GetChar() calls
643         pos_type i = 0;
644         if (i < size() && !isNewline(i)) {
645                 ++i;
646                 char previous_char = 0;
647                 char temp = 0;
648                 if (i < size()) {
649                         previous_char = getChar(i);
650                         if (!isNewline(i)) {
651                                 ++i;
652                                 while (i < size() && previous_char != ' ') {
653                                         temp = getChar(i);
654                                         if (isNewline(i))
655                                                 break;
656
657                                         ++i;
658                                         previous_char = temp;
659                                 }
660                         }
661                 }
662         }
663
664         return i;
665 }
666
667
668 // returns -1 if inset not found
669 int Paragraph::getPositionOfInset(InsetOld const * inset) const
670 {
671         // Find the entry.
672         InsetList::const_iterator it = insetlist.begin();
673         InsetList::const_iterator end = insetlist.end();
674         for (; it != end; ++it)
675                 if (it->inset == inset)
676                         return it->pos;
677         return -1;
678 }
679
680
681 InsetBibitem * Paragraph::bibitem() const
682 {
683         InsetList::const_iterator it = insetlist.begin();
684         if (it != insetlist.end() && it->inset->lyxCode() == InsetOld::BIBTEX_CODE)
685                 return static_cast<InsetBibitem *>(it->inset);
686         return 0;
687 }
688
689
690
691 // This could go to ParagraphParameters if we want to
692 int Paragraph::startTeXParParams(BufferParams const & bparams,
693                                  ostream & os, bool moving_arg) const
694 {
695         int column = 0;
696
697         if (params().noindent()) {
698                 os << "\\noindent ";
699                 column += 10;
700         }
701
702         switch (params().align()) {
703         case LYX_ALIGN_NONE:
704         case LYX_ALIGN_BLOCK:
705         case LYX_ALIGN_LAYOUT:
706         case LYX_ALIGN_SPECIAL:
707                 break;
708         case LYX_ALIGN_LEFT:
709         case LYX_ALIGN_RIGHT:
710         case LYX_ALIGN_CENTER:
711                 if (moving_arg) {
712                         os << "\\protect";
713                         column = 8;
714                 }
715                 break;
716         }
717
718         switch (params().align()) {
719         case LYX_ALIGN_NONE:
720         case LYX_ALIGN_BLOCK:
721         case LYX_ALIGN_LAYOUT:
722         case LYX_ALIGN_SPECIAL:
723                 break;
724         case LYX_ALIGN_LEFT:
725                 if (getParLanguage(bparams)->babel() != "hebrew") {
726                         os << "\\begin{flushleft}";
727                         column += 17;
728                 } else {
729                         os << "\\begin{flushright}";
730                         column += 18;
731                 }
732                 break;
733         case LYX_ALIGN_RIGHT:
734                 if (getParLanguage(bparams)->babel() != "hebrew") {
735                         os << "\\begin{flushright}";
736                         column += 18;
737                 } else {
738                         os << "\\begin{flushleft}";
739                         column += 17;
740                 }
741                 break;
742         case LYX_ALIGN_CENTER:
743                 os << "\\begin{center}";
744                 column += 14;
745                 break;
746         }
747
748         return column;
749 }
750
751
752 // This could go to ParagraphParameters if we want to
753 int Paragraph::endTeXParParams(BufferParams const & bparams,
754                                ostream & os, bool moving_arg) const
755 {
756         int column = 0;
757
758         switch (params().align()) {
759         case LYX_ALIGN_NONE:
760         case LYX_ALIGN_BLOCK:
761         case LYX_ALIGN_LAYOUT:
762         case LYX_ALIGN_SPECIAL:
763                 break;
764         case LYX_ALIGN_LEFT:
765         case LYX_ALIGN_RIGHT:
766         case LYX_ALIGN_CENTER:
767                 if (moving_arg) {
768                         os << "\\protect";
769                         column = 8;
770                 }
771                 break;
772         }
773
774         switch (params().align()) {
775         case LYX_ALIGN_NONE:
776         case LYX_ALIGN_BLOCK:
777         case LYX_ALIGN_LAYOUT:
778         case LYX_ALIGN_SPECIAL:
779                 break;
780         case LYX_ALIGN_LEFT:
781                 if (getParLanguage(bparams)->babel() != "hebrew") {
782                         os << "\\end{flushleft}";
783                         column = 15;
784                 } else {
785                         os << "\\end{flushright}";
786                         column = 16;
787                 }
788                 break;
789         case LYX_ALIGN_RIGHT:
790                 if (getParLanguage(bparams)->babel() != "hebrew") {
791                         os << "\\end{flushright}";
792                         column+= 16;
793                 } else {
794                         os << "\\end{flushleft}";
795                         column = 15;
796                 }
797                 break;
798         case LYX_ALIGN_CENTER:
799                 os << "\\end{center}";
800                 column = 12;
801                 break;
802         }
803         return column;
804 }
805
806
807 // This one spits out the text of the paragraph
808 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
809                                 BufferParams const & bparams,
810                                 LyXFont const & outerfont,
811                                 ostream & os, TexRow & texrow,
812                                 LatexRunParams const & runparams)
813 {
814         lyxerr[Debug::LATEX] << "SimpleTeXOnePar...     " << this << endl;
815
816         bool return_value = false;
817
818         LyXLayout_ptr style;
819
820         // well we have to check if we are in an inset with unlimited
821         // lenght (all in one row) if that is true then we don't allow
822         // any special options in the paragraph and also we don't allow
823         // any environment other then "Standard" to be valid!
824         bool asdefault =
825                 (inInset() && inInset()->forceDefaultParagraphs(inInset()));
826
827         if (asdefault) {
828                 style = bparams.getLyXTextClass().defaultLayout();
829         } else {
830                 style = layout();
831         }
832
833         LyXFont basefont;
834
835         // Maybe we have to create a optional argument.
836         pos_type body_pos;
837
838         // FIXME: can we actually skip this check and just call
839         // beginningOfBody() ??
840         if (style->labeltype != LABEL_MANUAL) {
841                 body_pos = 0;
842         } else {
843                 body_pos = beginningOfBody();
844         }
845
846         unsigned int column = 0;
847
848         if (body_pos > 0) {
849                 os << '[';
850                 ++column;
851                 basefont = getLabelFont(bparams, outerfont);
852         } else {
853                 basefont = getLayoutFont(bparams, outerfont);
854         }
855
856         bool moving_arg = runparams.moving_arg;
857         moving_arg |= style->needprotect;
858
859         // Which font is currently active?
860         LyXFont running_font(basefont);
861         // Do we have an open font change?
862         bool open_font = false;
863
864         Change::Type running_change = Change::UNCHANGED;
865
866         texrow.start(id(), 0);
867
868         // if the paragraph is empty, the loop will not be entered at all
869         if (empty()) {
870                 if (style->isCommand()) {
871                         os << '{';
872                         ++column;
873                 }
874                 if (!asdefault)
875                         column += startTeXParParams(bparams, os, moving_arg);
876
877         }
878
879         for (pos_type i = 0; i < size(); ++i) {
880                 ++column;
881                 // First char in paragraph or after label?
882                 if (i == body_pos) {
883                         if (body_pos > 0) {
884                                 if (open_font) {
885                                         column += running_font.latexWriteEndChanges(os, basefont, basefont);
886                                         open_font = false;
887                                 }
888                                 basefont = getLayoutFont(bparams, outerfont);
889                                 running_font = basefont;
890                                 os << ']';
891                                 ++column;
892                         }
893                         if (style->isCommand()) {
894                                 os << '{';
895                                 ++column;
896                         }
897
898                         if (!asdefault)
899                                 column += startTeXParParams(bparams, os,
900                                                             moving_arg);
901                 }
902
903                 value_type c = getChar(i);
904
905                 // Fully instantiated font
906                 LyXFont font = getFont(bparams, i, outerfont);
907
908                 LyXFont const last_font = running_font;
909
910                 // Spaces at end of font change are simulated to be
911                 // outside font change, i.e. we write "\textXX{text} "
912                 // rather than "\textXX{text }". (Asger)
913                 if (open_font && c == ' ' && i <= size() - 2) {
914                         LyXFont const & next_font = getFont(bparams, i + 1, outerfont);
915                         if (next_font != running_font
916                             && next_font != font) {
917                                 font = next_font;
918                         }
919                 }
920
921                 // We end font definition before blanks
922                 if (open_font &&
923                     (font != running_font ||
924                      font.language() != running_font.language()))
925                 {
926                         column += running_font.latexWriteEndChanges(os,
927                                                                     basefont,
928                                                                     (i == body_pos-1) ? basefont : font);
929                         running_font = basefont;
930                         open_font = false;
931                 }
932
933                 // Blanks are printed before start of fontswitch
934                 if (c == ' ') {
935                         // Do not print the separation of the optional argument
936                         if (i != body_pos - 1) {
937                                 pimpl_->simpleTeXBlanks(os, texrow, i,
938                                                        column, font, *style);
939                         }
940                 }
941
942                 // Do we need to change font?
943                 if ((font != running_font ||
944                      font.language() != running_font.language()) &&
945                         i != body_pos - 1)
946                 {
947                         column += font.latexWriteStartChanges(os, basefont,
948                                                               last_font);
949                         running_font = font;
950                         open_font = true;
951                 }
952
953                 Change::Type change = pimpl_->lookupChange(i);
954
955                 column += Changes::latexMarkChange(os, running_change, change);
956                 running_change = change;
957
958                 LatexRunParams rp = runparams;
959                 rp.moving_arg = moving_arg;
960                 rp.free_spacing = style->free_spacing;
961                 pimpl_->simpleTeXSpecialChars(buf, bparams,
962                                               os, texrow, runparams,
963                                               font, running_font,
964                                               basefont, outerfont, open_font,
965                                               running_change,
966                                               *style, i, column, c);
967         }
968
969         column += Changes::latexMarkChange(os,
970                         running_change, Change::UNCHANGED);
971
972         // If we have an open font definition, we have to close it
973         if (open_font) {
974 #ifdef FIXED_LANGUAGE_END_DETECTION
975                 if (next_) {
976                         running_font
977                                 .latexWriteEndChanges(os, basefont,
978                                                       next_->getFont(bparams,
979                                                       0, outerfont));
980                 } else {
981                         running_font.latexWriteEndChanges(os, basefont,
982                                                           basefont);
983                 }
984 #else
985 #ifdef WITH_WARNINGS
986 //#warning For now we ALWAYS have to close the foreign font settings if they are
987 //#warning there as we start another \selectlanguage with the next paragraph if
988 //#warning we are in need of this. This should be fixed sometime (Jug)
989 #endif
990                 running_font.latexWriteEndChanges(os, basefont,  basefont);
991 #endif
992         }
993
994         // Needed if there is an optional argument but no contents.
995         if (body_pos > 0 && body_pos == size()) {
996                 os << "]~";
997                 return_value = false;
998         }
999
1000         if (!asdefault) {
1001                 column += endTeXParParams(bparams, os, moving_arg);
1002         }
1003
1004         lyxerr[Debug::LATEX] << "SimpleTeXOnePar...done " << this << endl;
1005         return return_value;
1006 }
1007
1008
1009 namespace {
1010
1011 /// return true if the char is a meta-character for an inset
1012 inline
1013 bool IsInsetChar(char c)
1014 {
1015         return (c == Paragraph::META_INSET);
1016 }
1017
1018 } // namespace anon
1019
1020
1021
1022 bool Paragraph::isHfill(pos_type pos) const
1023 {
1024         return IsInsetChar(getChar(pos))
1025                && getInset(pos)->lyxCode() == InsetOld::HFILL_CODE;
1026 }
1027
1028
1029 bool Paragraph::isInset(pos_type pos) const
1030 {
1031         return IsInsetChar(getChar(pos));
1032 }
1033
1034
1035 bool Paragraph::isNewline(pos_type pos) const
1036 {
1037         return IsInsetChar(getChar(pos))
1038                && getInset(pos)->lyxCode() == InsetOld::NEWLINE_CODE;
1039 }
1040
1041
1042 bool Paragraph::isSeparator(pos_type pos) const
1043 {
1044         return IsSeparatorChar(getChar(pos));
1045 }
1046
1047
1048 bool Paragraph::isLineSeparator(pos_type pos) const
1049 {
1050         value_type const c = getChar(pos);
1051         return IsLineSeparatorChar(c)
1052                 || (IsInsetChar(c) && getInset(pos) &&
1053                 getInset(pos)->isLineSeparator());
1054 }
1055
1056
1057 bool Paragraph::isKomma(pos_type pos) const
1058 {
1059         return IsKommaChar(getChar(pos));
1060 }
1061
1062
1063 /// Used by the spellchecker
1064 bool Paragraph::isLetter(pos_type pos) const
1065 {
1066         value_type const c = getChar(pos);
1067         if (IsLetterChar(c))
1068                 return true;
1069         if (isInset(pos))
1070                 return getInset(pos)->isLetter();
1071         // We want to pass the ' and escape chars to ispell
1072         string const extra = lyxrc.isp_esc_chars + '\'';
1073         return contains(extra, c);
1074 }
1075
1076
1077 bool Paragraph::isWord(pos_type pos) const
1078 {
1079         unsigned char const c = getChar(pos);
1080         return !(IsSeparatorChar(c)
1081                   || IsKommaChar(c)
1082                   || IsInsetChar(c));
1083 }
1084
1085
1086 Language const *
1087 Paragraph::getParLanguage(BufferParams const & bparams) const
1088 {
1089         if (!empty())
1090                 return getFirstFontSettings().language();
1091 #warning FIXME we should check the prev par as well (Lgb)
1092         return bparams.language;
1093 }
1094
1095
1096 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
1097 {
1098         return lyxrc.rtl_support
1099                 && getParLanguage(bparams)->RightToLeft()
1100                 && !(inInset() && inInset()->owner() &&
1101                      inInset()->owner()->lyxCode() == InsetOld::ERT_CODE);
1102 }
1103
1104
1105 void Paragraph::changeLanguage(BufferParams const & bparams,
1106                                Language const * from, Language const * to)
1107 {
1108         for (pos_type i = 0; i < size(); ++i) {
1109                 LyXFont font = getFontSettings(bparams, i);
1110                 if (font.language() == from) {
1111                         font.setLanguage(to);
1112                         setFont(i, font);
1113                 }
1114         }
1115 }
1116
1117
1118 bool Paragraph::isMultiLingual(BufferParams const & bparams)
1119 {
1120         Language const * doc_language = bparams.language;
1121         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1122         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1123
1124         for (; cit != end; ++cit)
1125                 if (cit->font().language() != ignore_language &&
1126                     cit->font().language() != latex_language &&
1127                     cit->font().language() != doc_language)
1128                         return true;
1129         return false;
1130 }
1131
1132
1133 // Convert the paragraph to a string.
1134 // Used for building the table of contents
1135 string const Paragraph::asString(Buffer const & buffer, bool label) const
1136 {
1137 #if 0
1138         string s;
1139         if (label && !params().labelString().empty())
1140                 s += params().labelString() + ' ';
1141
1142         for (pos_type i = 0; i < size(); ++i) {
1143                 value_type c = getChar(i);
1144                 if (IsPrintable(c))
1145                         s += c;
1146                 else if (c == META_INSET &&
1147                          getInset(i)->lyxCode() == InsetOld::MATH_CODE) {
1148                         ostringstream ost;
1149                         getInset(i)->ascii(buffer, ost);
1150                         s += subst(STRCONV(ost.str()),'\n',' ');
1151                 }
1152         }
1153
1154         return s;
1155 #else
1156         // This should really be done by the caller and not here.
1157         string ret(asString(buffer, 0, size(), label));
1158         return subst(ret, '\n', ' ');
1159 #endif
1160 }
1161
1162
1163 string const Paragraph::asString(Buffer const & buffer,
1164                                  pos_type beg, pos_type end, bool label) const
1165 {
1166         ostringstream os;
1167
1168         if (beg == 0 && label && !params().labelString().empty())
1169                 os << params().labelString() << ' ';
1170
1171         for (pos_type i = beg; i < end; ++i) {
1172                 value_type const c = getUChar(buffer.params(), i);
1173                 if (IsPrintable(c))
1174                         os << c;
1175                 else if (c == META_INSET)
1176                         getInset(i)->ascii(buffer, os);
1177         }
1178
1179         return os.str();
1180 }
1181
1182
1183 void Paragraph::setInsetOwner(UpdatableInset * inset)
1184 {
1185         pimpl_->inset_owner = inset;
1186         InsetList::iterator it = insetlist.begin();
1187         InsetList::iterator end = insetlist.end();
1188         for (; it != end; ++it)
1189                 if (it->inset)
1190                         it->inset->setOwner(inset);
1191 }
1192
1193
1194 void Paragraph::deleteInsetsLyXText(BufferView * bv)
1195 {
1196         insetlist.deleteInsetsLyXText(bv);
1197 }
1198
1199
1200 void Paragraph::setContentsFromPar(Paragraph const & par)
1201 {
1202         pimpl_->setContentsFromPar(par);
1203 }
1204
1205
1206 void Paragraph::trackChanges(Change::Type type)
1207 {
1208         pimpl_->trackChanges(type);
1209 }
1210
1211
1212 void Paragraph::untrackChanges()
1213 {
1214         pimpl_->untrackChanges();
1215 }
1216
1217
1218 void Paragraph::cleanChanges()
1219 {
1220         pimpl_->cleanChanges();
1221 }
1222
1223
1224 Change::Type Paragraph::lookupChange(lyx::pos_type pos) const
1225 {
1226         BOOST_ASSERT(!size() || pos < size());
1227         return pimpl_->lookupChange(pos);
1228 }
1229
1230
1231 Change const Paragraph::lookupChangeFull(lyx::pos_type pos) const
1232 {
1233         BOOST_ASSERT(!size() || pos < size());
1234         return pimpl_->lookupChangeFull(pos);
1235 }
1236
1237
1238 bool Paragraph::isChanged(pos_type start, pos_type end) const
1239 {
1240         return pimpl_->isChanged(start, end);
1241 }
1242
1243
1244 bool Paragraph::isChangeEdited(pos_type start, pos_type end) const
1245 {
1246         return pimpl_->isChangeEdited(start, end);
1247 }
1248
1249
1250 void Paragraph::setChange(lyx::pos_type pos, Change::Type type)
1251 {
1252         pimpl_->setChange(pos, type);
1253
1254 }
1255
1256
1257 void Paragraph::markErased()
1258 {
1259         pimpl_->markErased();
1260 }
1261
1262
1263 void Paragraph::acceptChange(pos_type start, pos_type end)
1264 {
1265         return pimpl_->acceptChange(start, end);
1266 }
1267
1268
1269 void Paragraph::rejectChange(pos_type start, pos_type end)
1270 {
1271         return pimpl_->rejectChange(start, end);
1272 }
1273
1274
1275 Paragraph::value_type Paragraph::getChar(pos_type pos) const
1276 {
1277         // This is in the critical path!
1278         pos_type const siz = text_.size();
1279
1280         BOOST_ASSERT(pos <= siz);
1281
1282         if (pos == siz) {
1283                 lyxerr << "getChar() on pos " << pos << " in par id "
1284                        << id() << " of size " << siz
1285                        << "  is a bit silly !" << endl;
1286                 return '\0';
1287         }
1288
1289         return text_[pos];
1290 }
1291
1292
1293 int Paragraph::id() const
1294 {
1295         return pimpl_->id_;
1296 }
1297
1298
1299 void Paragraph::id(int i)
1300 {
1301         pimpl_->id_ = i;
1302 }
1303
1304
1305 LyXLayout_ptr const & Paragraph::layout() const
1306 {
1307 /*
1308         InsetOld * inset = inInset();
1309         if (inset && inset->lyxCode() == InsetOld::ENVIRONMENT_CODE)
1310                 return static_cast<InsetEnvironment*>(inset)->layout();
1311 */
1312         return layout_;
1313 }
1314
1315
1316 void Paragraph::layout(LyXLayout_ptr const & new_layout)
1317 {
1318         layout_ = new_layout;
1319 }
1320
1321
1322 UpdatableInset * Paragraph::inInset() const
1323 {
1324         return pimpl_->inset_owner;
1325 }
1326
1327
1328 void Paragraph::clearContents()
1329 {
1330         text_.clear();
1331 }
1332
1333
1334 void Paragraph::setChar(pos_type pos, value_type c)
1335 {
1336         text_[pos] = c;
1337 }
1338
1339
1340 ParagraphParameters & Paragraph::params()
1341 {
1342         return pimpl_->params;
1343 }
1344
1345
1346 ParagraphParameters const & Paragraph::params() const
1347 {
1348         return pimpl_->params;
1349 }
1350
1351
1352 bool Paragraph::isFreeSpacing() const
1353 {
1354         if (layout()->free_spacing)
1355                 return true;
1356
1357         // for now we just need this, later should we need this in some
1358         // other way we can always add a function to InsetOld too.
1359         if (pimpl_->inset_owner && pimpl_->inset_owner->owner())
1360                 return pimpl_->inset_owner->owner()->lyxCode() == InsetOld::ERT_CODE;
1361         return false;
1362 }
1363
1364
1365 bool Paragraph::allowEmpty() const
1366 {
1367         if (layout()->keepempty)
1368                 return true;
1369         if (pimpl_->inset_owner && pimpl_->inset_owner->owner())
1370                 return pimpl_->inset_owner->owner()->lyxCode() == InsetOld::ERT_CODE;
1371         return false;
1372 }