scramble email addresses
[lyx.git] / src / Paragraph.cpp
blob2bd9f45f2488064b5d453c2bd74dd787fbfda86f
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.
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
15 * Full author contact details are available in file CREDITS.
18 #include <config.h>
20 #include "Paragraph.h"
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "Changes.h"
25 #include "Counters.h"
26 #include "Encoding.h"
27 #include "debug.h"
28 #include "gettext.h"
29 #include "InsetList.h"
30 #include "Language.h"
31 #include "LaTeXFeatures.h"
32 #include "Color.h"
33 #include "Layout.h"
34 #include "Length.h"
35 #include "Font.h"
36 #include "FontList.h"
37 #include "LyXRC.h"
38 #include "Messages.h"
39 #include "OutputParams.h"
40 #include "output_latex.h"
41 #include "paragraph_funcs.h"
42 #include "ParagraphParameters.h"
43 #include "sgml.h"
44 #include "TexRow.h"
45 #include "VSpace.h"
47 #include "frontends/alert.h"
48 #include "frontends/FontMetrics.h"
50 #include "insets/InsetBibitem.h"
51 #include "insets/InsetOptArg.h"
53 #include "support/lstrings.h"
54 #include "support/textutils.h"
55 #include "support/convert.h"
56 #include "support/unicode.h"
58 #include <sstream>
60 using std::endl;
61 using std::string;
62 using std::ostream;
64 namespace lyx {
66 using support::contains;
67 using support::prefixIs;
68 using support::suffixIs;
69 using support::rsplit;
70 using support::rtrim;
73 /////////////////////////////////////////////////////////////////////
75 // Paragraph::Private
77 /////////////////////////////////////////////////////////////////////
79 class Paragraph::Private
81 public:
82 ///
83 Private(Paragraph * owner);
84 /// "Copy constructor"
85 Private(Private const &, Paragraph * owner);
87 ///
88 value_type getChar(pos_type pos) const;
89 ///
90 void insertChar(pos_type pos, value_type c, Change const & change);
92 /// Output the surrogate pair formed by \p c and \p next to \p os.
93 /// \return the number of characters written.
94 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
95 Encoding const &);
97 /// Output a space in appropriate formatting (or a surrogate pair
98 /// if the next character is a combining character).
99 /// \return whether a surrogate pair was output.
100 bool simpleTeXBlanks(Encoding const &,
101 odocstream &, TexRow & texrow,
102 pos_type i,
103 unsigned int & column,
104 Font const & font,
105 Layout const & style);
107 /// Output consecutive known unicode chars, belonging to the same
108 /// language as specified by \p preamble, to \p os starting from \p c.
109 /// \return the number of characters written.
110 int knownLangChars(odocstream & os, value_type c, string & preamble,
111 Change &, Encoding const &, pos_type &);
113 void latexInset(Buffer const &, BufferParams const &,
114 odocstream &,
115 TexRow & texrow, OutputParams &,
116 Font & running_font,
117 Font & basefont,
118 Font const & outerfont,
119 bool & open_font,
120 Change & running_change,
121 Layout const & style,
122 pos_type & i,
123 unsigned int & column);
126 void latexSpecialChar(
127 odocstream & os,
128 OutputParams & runparams,
129 Font & running_font,
130 Change & running_change,
131 Layout const & style,
132 pos_type & i,
133 unsigned int & column);
136 void validate(LaTeXFeatures & features,
137 Layout const & layout) const;
140 pos_type size() const { return owner_->size(); }
142 /// match a string against a particular point in the paragraph
143 bool isTextAt(std::string const & str, pos_type pos) const;
145 /// Which Paragraph owns us?
146 Paragraph * owner_;
148 /// In which Inset?
149 Inset * inset_owner_;
152 FontList fontlist_;
155 unsigned int id_;
157 static unsigned int paragraph_id;
159 ParagraphParameters params_;
161 /// for recording and looking up changes
162 Changes changes_;
165 InsetList insetlist_;
171 using std::endl;
172 using std::upper_bound;
173 using std::lower_bound;
174 using std::string;
177 // Initialization of the counter for the paragraph id's,
178 unsigned int Paragraph::Private::paragraph_id = 0;
180 namespace {
182 struct special_phrase {
183 string phrase;
184 docstring macro;
185 bool builtin;
188 special_phrase const special_phrases[] = {
189 { "LyX", from_ascii("\\protect\\LyX{}"), false },
190 { "TeX", from_ascii("\\protect\\TeX{}"), true },
191 { "LaTeX2e", from_ascii("\\protect\\LaTeXe{}"), true },
192 { "LaTeX", from_ascii("\\protect\\LaTeX{}"), true },
195 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
197 } // namespace anon
200 Paragraph::Private::Private(Paragraph * owner)
201 : owner_(owner)
203 inset_owner_ = 0;
204 id_ = paragraph_id++;
208 Paragraph::Private::Private(Private const & p, Paragraph * owner)
209 : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_),
210 params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_)
212 id_ = paragraph_id++;
216 bool Paragraph::isChanged(pos_type start, pos_type end) const
218 BOOST_ASSERT(start >= 0 && start <= size());
219 BOOST_ASSERT(end > start && end <= size() + 1);
221 return d->changes_.isChanged(start, end);
225 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const {
226 // keep the logic here in sync with the logic of eraseChars()
228 if (!trackChanges) {
229 return true;
232 Change change = d->changes_.lookup(size());
234 return change.type == Change::INSERTED && change.author == 0;
238 void Paragraph::setChange(Change const & change)
240 // beware of the imaginary end-of-par character!
241 d->changes_.set(change, 0, size() + 1);
244 * Propagate the change recursively - but not in case of DELETED!
246 * Imagine that your co-author makes changes in an existing inset. He
247 * sends your document to you and you come to the conclusion that the
248 * inset should go completely. If you erase it, LyX must not delete all
249 * text within the inset. Otherwise, the change tracked insertions of
250 * your co-author get lost and there is no way to restore them later.
252 * Conclusion: An inset's content should remain untouched if you delete it
255 if (change.type != Change::DELETED) {
256 for (pos_type pos = 0; pos < size(); ++pos) {
257 if (isInset(pos))
258 getInset(pos)->setChange(change);
264 void Paragraph::setChange(pos_type pos, Change const & change)
266 BOOST_ASSERT(pos >= 0 && pos <= size());
268 d->changes_.set(change, pos);
270 // see comment in setChange(Change const &) above
272 if (change.type != Change::DELETED &&
273 pos < size() && isInset(pos)) {
274 getInset(pos)->setChange(change);
279 Change const & Paragraph::lookupChange(pos_type pos) const
281 BOOST_ASSERT(pos >= 0 && pos <= size());
283 return d->changes_.lookup(pos);
287 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start,
288 pos_type end)
290 BOOST_ASSERT(start >= 0 && start <= size());
291 BOOST_ASSERT(end > start && end <= size() + 1);
293 for (pos_type pos = start; pos < end; ++pos) {
294 switch (lookupChange(pos).type) {
295 case Change::UNCHANGED:
296 // accept changes in nested inset
297 if (pos < size() && isInset(pos))
298 getInset(pos)->acceptChanges(bparams);
300 break;
302 case Change::INSERTED:
303 d->changes_.set(Change(Change::UNCHANGED), pos);
304 // also accept changes in nested inset
305 if (pos < size() && isInset(pos)) {
306 getInset(pos)->acceptChanges(bparams);
308 break;
310 case Change::DELETED:
311 // Suppress access to non-existent
312 // "end-of-paragraph char"
313 if (pos < size()) {
314 eraseChar(pos, false);
315 --end;
316 --pos;
318 break;
325 void Paragraph::rejectChanges(BufferParams const & bparams,
326 pos_type start, pos_type end)
328 BOOST_ASSERT(start >= 0 && start <= size());
329 BOOST_ASSERT(end > start && end <= size() + 1);
331 for (pos_type pos = start; pos < end; ++pos) {
332 switch (lookupChange(pos).type) {
333 case Change::UNCHANGED:
334 // reject changes in nested inset
335 if (pos < size() && isInset(pos)) {
336 getInset(pos)->rejectChanges(bparams);
338 break;
340 case Change::INSERTED:
341 // Suppress access to non-existent
342 // "end-of-paragraph char"
343 if (pos < size()) {
344 eraseChar(pos, false);
345 --end;
346 --pos;
348 break;
350 case Change::DELETED:
351 d->changes_.set(Change(Change::UNCHANGED), pos);
353 // Do NOT reject changes within a deleted inset!
354 // There may be insertions of a co-author inside of it!
356 break;
362 Paragraph::value_type Paragraph::Private::getChar(pos_type pos) const
364 BOOST_ASSERT(pos >= 0 && pos <= size());
366 return owner_->getChar(pos);
370 void Paragraph::Private::insertChar(pos_type pos, value_type c,
371 Change const & change)
373 BOOST_ASSERT(pos >= 0 && pos <= size());
375 // track change
376 changes_.insert(change, pos);
378 // This is actually very common when parsing buffers (and
379 // maybe inserting ascii text)
380 if (pos == size()) {
381 // when appending characters, no need to update tables
382 owner_->text_.push_back(c);
383 return;
386 owner_->text_.insert(owner_->text_.begin() + pos, c);
388 // Update the font table.
389 fontlist_.increasePosAfterPos(pos);
391 // Update the insets
392 insetlist_.increasePosAfterPos(pos);
396 void Paragraph::insertInset(pos_type pos, Inset * inset,
397 Change const & change)
399 BOOST_ASSERT(inset);
400 BOOST_ASSERT(pos >= 0 && pos <= size());
402 d->insertChar(pos, META_INSET, change);
403 BOOST_ASSERT(text_[pos] == META_INSET);
405 // Add a new entry in the insetlist_.
406 d->insetlist_.insert(inset, pos);
410 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
412 BOOST_ASSERT(pos >= 0 && pos <= size());
414 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
416 if (trackChanges) {
417 Change change = d->changes_.lookup(pos);
419 // set the character to DELETED if
420 // a) it was previously unchanged or
421 // b) it was inserted by a co-author
423 if (change.type == Change::UNCHANGED ||
424 (change.type == Change::INSERTED && change.author != 0)) {
425 setChange(pos, Change(Change::DELETED));
426 return false;
429 if (change.type == Change::DELETED)
430 return false;
433 // Don't physically access the imaginary end-of-paragraph character.
434 // eraseChar() can only mark it as DELETED. A physical deletion of
435 // end-of-par must be handled externally.
436 if (pos == size()) {
437 return false;
440 // track change
441 d->changes_.erase(pos);
443 // if it is an inset, delete the inset entry
444 if (text_[pos] == Paragraph::META_INSET)
445 d->insetlist_.erase(pos);
447 text_.erase(text_.begin() + pos);
449 // Update the fontlist_
450 d->fontlist_.erase(pos);
452 // Update the insetlist_
453 d->insetlist_.decreasePosAfterPos(pos);
455 return true;
459 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
461 BOOST_ASSERT(start >= 0 && start <= size());
462 BOOST_ASSERT(end >= start && end <= size() + 1);
464 pos_type i = start;
465 for (pos_type count = end - start; count; --count) {
466 if (!eraseChar(i, trackChanges))
467 ++i;
469 return end - i;
473 int Paragraph::Private::latexSurrogatePair(odocstream & os, value_type c,
474 value_type next, Encoding const & encoding)
476 // Writing next here may circumvent a possible font change between
477 // c and next. Since next is only output if it forms a surrogate pair
478 // with c we can ignore this:
479 // A font change inside a surrogate pair does not make sense and is
480 // hopefully impossible to input.
481 // FIXME: change tracking
482 // Is this correct WRT change tracking?
483 docstring const latex1 = encoding.latexChar(next);
484 docstring const latex2 = encoding.latexChar(c);
485 os << latex1 << '{' << latex2 << '}';
486 return latex1.length() + latex2.length() + 2;
490 bool Paragraph::Private::simpleTeXBlanks(Encoding const & encoding,
491 odocstream & os, TexRow & texrow,
492 pos_type i,
493 unsigned int & column,
494 Font const & font,
495 Layout const & style)
497 if (style.pass_thru)
498 return false;
500 if (i + 1 < size()) {
501 char_type next = getChar(i + 1);
502 if (Encodings::isCombiningChar(next)) {
503 // This space has an accent, so we must always output it.
504 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
505 return true;
509 if (lyxrc.plaintext_linelen > 0
510 && column > lyxrc.plaintext_linelen
511 && i
512 && getChar(i - 1) != ' '
513 && (i + 1 < size())
514 // same in FreeSpacing mode
515 && !owner_->isFreeSpacing()
516 // In typewriter mode, we want to avoid
517 // ! . ? : at the end of a line
518 && !(font.family() == Font::TYPEWRITER_FAMILY
519 && (getChar(i - 1) == '.'
520 || getChar(i - 1) == '?'
521 || getChar(i - 1) == ':'
522 || getChar(i - 1) == '!'))) {
523 os << '\n';
524 texrow.newline();
525 texrow.start(owner_->id(), i + 1);
526 column = 0;
527 } else if (style.free_spacing) {
528 os << '~';
529 } else {
530 os << ' ';
532 return false;
536 int Paragraph::Private::knownLangChars(odocstream & os,
537 value_type c,
538 string & preamble,
539 Change & runningChange,
540 Encoding const & encoding,
541 pos_type & i)
543 // When the character is marked by the proper language, we simply
544 // get its code point in some encoding, otherwise we get the
545 // translation specified in the unicodesymbols file, which is
546 // something like "\textLANG{<spec>}". So, we have to retain
547 // "\textLANG{<spec>" for the first char but only "<spec>" for
548 // all subsequent chars.
549 docstring const latex1 = rtrim(encoding.latexChar(c), "}");
550 int length = latex1.length();
551 os << latex1;
552 while (i + 1 < size()) {
553 char_type next = getChar(i + 1);
554 // Stop here if next character belongs to another
555 // language or there is a change tracking status.
556 if (!Encodings::isKnownLangChar(next, preamble) ||
557 runningChange != owner_->lookupChange(i + 1))
558 break;
559 Font prev_font;
560 bool found = false;
561 FontList::const_iterator cit = fontlist_.begin();
562 FontList::const_iterator end = fontlist_.end();
563 for (; cit != end; ++cit) {
564 if (cit->pos() >= i && !found) {
565 prev_font = cit->font();
566 found = true;
568 if (cit->pos() >= i + 1)
569 break;
571 // Stop here if there is a font attribute change.
572 if (found && cit != end && prev_font != cit->font())
573 break;
574 docstring const latex = rtrim(encoding.latexChar(next), "}");
575 docstring::size_type const j =
576 latex.find_first_of(from_ascii("{"));
577 if (j == docstring::npos) {
578 os << latex;
579 length += latex.length();
580 } else {
581 os << latex.substr(j + 1);
582 length += latex.substr(j + 1).length();
584 ++i;
586 // When the proper language is set, we are simply passed a code
587 // point, so we should not try to close the \textLANG command.
588 if (prefixIs(latex1, from_ascii("\\" + preamble))) {
589 os << '}';
590 ++length;
592 return length;
596 bool Paragraph::Private::isTextAt(string const & str, pos_type pos) const
598 pos_type const len = str.length();
600 // is the paragraph large enough?
601 if (pos + len > size())
602 return false;
604 // does the wanted text start at point?
605 for (string::size_type i = 0; i < str.length(); ++i) {
606 // Caution: direct comparison of characters works only
607 // because str is pure ASCII.
608 if (str[i] != owner_->text_[pos + i])
609 return false;
612 return fontlist_.hasChangeInRange(pos, len);
616 void Paragraph::Private::latexInset(Buffer const & buf,
617 BufferParams const & bparams,
618 odocstream & os,
619 TexRow & texrow,
620 OutputParams & runparams,
621 Font & running_font,
622 Font & basefont,
623 Font const & outerfont,
624 bool & open_font,
625 Change & running_change,
626 Layout const & style,
627 pos_type & i,
628 unsigned int & column)
630 Inset * inset = owner_->getInset(i);
631 BOOST_ASSERT(inset);
633 if (style.pass_thru) {
634 inset->plaintext(buf, os, runparams);
635 return;
638 // FIXME: move this to InsetNewline::latex
639 if (inset->lyxCode() == NEWLINE_CODE) {
640 // newlines are handled differently here than
641 // the default in simpleTeXSpecialChars().
642 if (!style.newline_allowed) {
643 os << '\n';
644 } else {
645 if (open_font) {
646 column += running_font.latexWriteEndChanges(
647 os, bparams, runparams,
648 basefont, basefont);
649 open_font = false;
652 if (running_font.family() == Font::TYPEWRITER_FAMILY)
653 os << '~';
655 basefont = owner_->getLayoutFont(bparams, outerfont);
656 running_font = basefont;
658 if (runparams.moving_arg)
659 os << "\\protect ";
661 os << "\\\\\n";
663 texrow.newline();
664 texrow.start(owner_->id(), i + 1);
665 column = 0;
668 if (owner_->lookupChange(i).type == Change::DELETED) {
669 if( ++runparams.inDeletedInset == 1)
670 runparams.changeOfDeletedInset = owner_->lookupChange(i);
673 if (inset->canTrackChanges()) {
674 column += Changes::latexMarkChange(os, bparams, running_change,
675 Change(Change::UNCHANGED));
676 running_change = Change(Change::UNCHANGED);
679 bool close = false;
680 odocstream::pos_type const len = os.tellp();
682 if ((inset->lyxCode() == GRAPHICS_CODE
683 || inset->lyxCode() == MATH_CODE
684 || inset->lyxCode() == HYPERLINK_CODE)
685 && running_font.isRightToLeft()) {
686 if (running_font.language()->lang() == "farsi")
687 os << "\\beginL{}";
688 else
689 os << "\\L{";
690 close = true;
693 // FIXME: Bug: we can have an empty font change here!
694 // if there has just been a font change, we are going to close it
695 // right now, which means stupid latex code like \textsf{}. AFAIK,
696 // this does not harm dvi output. A minor bug, thus (JMarc)
698 // Some insets cannot be inside a font change command.
699 // However, even such insets *can* be placed in \L or \R
700 // or their equivalents (for RTL language switches), so we don't
701 // close the language in those cases.
702 // ArabTeX, though, cannot handle this special behavior, it seems.
703 bool arabtex = basefont.language()->lang() == "arabic_arabtex"
704 || running_font.language()->lang() == "arabic_arabtex";
705 if (open_font && inset->noFontChange()) {
706 bool closeLanguage = arabtex
707 || basefont.isRightToLeft() == running_font.isRightToLeft();
708 unsigned int count = running_font.latexWriteEndChanges(os,
709 bparams, runparams, basefont, basefont, closeLanguage);
710 column += count;
711 // if any font properties were closed, update the running_font,
712 // making sure, however, to leave the language as it was
713 if (count > 0) {
714 // FIXME: probably a better way to keep track of the old
715 // language, than copying the entire font?
716 Font const copy_font(running_font);
717 basefont = owner_->getLayoutFont(bparams, outerfont);
718 running_font = basefont;
719 if (!closeLanguage)
720 running_font.setLanguage(copy_font.language());
721 // leave font open if language is still open
722 open_font = (running_font.language() == basefont.language());
723 if (closeLanguage)
724 runparams.local_font = &basefont;
728 int tmp = inset->latex(buf, os, runparams);
730 if (close) {
731 if (running_font.language()->lang() == "farsi")
732 os << "\\endL{}";
733 else
734 os << '}';
737 if (tmp) {
738 for (int j = 0; j < tmp; ++j)
739 texrow.newline();
741 texrow.start(owner_->id(), i + 1);
742 column = 0;
743 } else {
744 column += os.tellp() - len;
747 if (owner_->lookupChange(i).type == Change::DELETED)
748 --runparams.inDeletedInset;
752 void Paragraph::Private::latexSpecialChar(
753 odocstream & os,
754 OutputParams & runparams,
755 Font & running_font,
756 Change & running_change,
757 Layout const & style,
758 pos_type & i,
759 unsigned int & column)
761 value_type const c = getChar(i);
763 if (style.pass_thru) {
764 if (c != '\0')
765 // FIXME UNICODE: This can fail if c cannot
766 // be encoded in the current encoding.
767 os.put(c);
768 return;
771 if (runparams.verbatim) {
772 os.put(c);
773 return;
776 switch (c) {
777 case '\\':
778 os << "\\textbackslash{}";
779 column += 15;
780 break;
782 case '|':
783 case '<':
784 case '>':
785 // In T1 encoding, these characters exist
786 if (lyxrc.fontenc == "T1") {
787 os.put(c);
788 //... but we should avoid ligatures
789 if ((c == '>' || c == '<')
790 && i <= size() - 2
791 && getChar(i + 1) == c) {
792 //os << "\\textcompwordmark{}";
793 //column += 19;
794 // Jean-Marc, have a look at
795 // this. I think this works
796 // equally well:
797 os << "\\,{}";
798 // Lgb
799 column += 3;
801 break;
803 // Typewriter font also has them
804 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
805 os.put(c);
806 break;
808 // Otherwise, we use what LaTeX
809 // provides us.
810 switch (c) {
811 case '<':
812 os << "\\textless{}";
813 column += 10;
814 break;
815 case '>':
816 os << "\\textgreater{}";
817 column += 13;
818 break;
819 case '|':
820 os << "\\textbar{}";
821 column += 9;
822 break;
824 break;
826 case '-': // "--" in Typewriter mode -> "-{}-"
827 if (i <= size() - 2 &&
828 getChar(i + 1) == '-' &&
829 running_font.family() == Font::TYPEWRITER_FAMILY) {
830 os << "-{}";
831 column += 2;
832 } else {
833 os << '-';
835 break;
837 case '\"':
838 os << "\\char`\\\"{}";
839 column += 9;
840 break;
842 case '$': case '&':
843 case '%': case '#': case '{':
844 case '}': case '_':
845 os << '\\';
846 os.put(c);
847 column += 1;
848 break;
850 case '~':
851 os << "\\textasciitilde{}";
852 column += 16;
853 break;
855 case '^':
856 os << "\\textasciicircum{}";
857 column += 17;
858 break;
860 case '*': case '[':
861 // avoid being mistaken for optional arguments
862 os << '{';
863 os.put(c);
864 os << '}';
865 column += 2;
866 break;
868 case ' ':
869 // Blanks are printed before font switching.
870 // Sure? I am not! (try nice-latex)
871 // I am sure it's correct. LyX might be smarter
872 // in the future, but for now, nothing wrong is
873 // written. (Asger)
874 break;
876 default:
877 // I assume this is hack treating typewriter as verbatim
878 // FIXME UNICODE: This can fail if c cannot be encoded
879 // in the current encoding.
880 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
881 if (c != '\0')
882 os.put(c);
883 break;
886 // LyX, LaTeX etc.
888 // FIXME: if we have "LaTeX" with a font
889 // change in the middle (before the 'T', then
890 // the "TeX" part is still special cased.
891 // Really we should only operate this on
892 // "words" for some definition of word
894 size_t pnr = 0;
896 for (; pnr < phrases_nr; ++pnr) {
897 if (isTextAt(special_phrases[pnr].phrase, i)) {
898 os << special_phrases[pnr].macro;
899 i += special_phrases[pnr].phrase.length() - 1;
900 column += special_phrases[pnr].macro.length() - 1;
901 break;
905 if (pnr == phrases_nr && c != '\0') {
906 Encoding const & encoding = *(runparams.encoding);
907 if (i + 1 < size()) {
908 char_type next = getChar(i + 1);
909 if (Encodings::isCombiningChar(next)) {
910 column += latexSurrogatePair(os, c, next, encoding) - 1;
911 ++i;
912 break;
915 string preamble;
916 if (Encodings::isKnownLangChar(c, preamble)) {
917 column += knownLangChars(os, c, preamble, running_change,
918 encoding, i) - 1;
919 break;
921 docstring const latex = encoding.latexChar(c);
922 if (latex.length() > 1 && latex[latex.length() - 1] != '}') {
923 // Prevent eating of a following
924 // space or command corruption by
925 // following characters
926 column += latex.length() + 1;
927 os << latex << "{}";
928 } else {
929 column += latex.length() - 1;
930 os << latex;
933 break;
938 void Paragraph::Private::validate(LaTeXFeatures & features,
939 Layout const & layout) const
941 // check the params.
942 if (!params_.spacing().isDefault())
943 features.require("setspace");
945 // then the layouts
946 features.useLayout(layout.name());
948 // then the fonts
949 fontlist_.validate(features);
951 // then the indentation
952 if (!params_.leftIndent().zero())
953 features.require("ParagraphLeftIndent");
955 // then the insets
956 InsetList::const_iterator icit = insetlist_.begin();
957 InsetList::const_iterator iend = insetlist_.end();
958 for (; icit != iend; ++icit) {
959 if (icit->inset) {
960 icit->inset->validate(features);
961 if (layout.needprotect &&
962 icit->inset->lyxCode() == FOOT_CODE)
963 features.require("NeedLyXFootnoteCode");
967 // then the contents
968 for (pos_type i = 0; i < size() ; ++i) {
969 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
970 if (!special_phrases[pnr].builtin
971 && isTextAt(special_phrases[pnr].phrase, i)) {
972 features.require(special_phrases[pnr].phrase);
973 break;
976 Encodings::validate(getChar(i), features);
980 /////////////////////////////////////////////////////////////////////
982 // Paragraph
984 /////////////////////////////////////////////////////////////////////
986 Paragraph::Paragraph()
987 : begin_of_body_(0), d(new Paragraph::Private(this))
989 itemdepth = 0;
990 d->params_.clear();
991 text_.reserve(100);
995 Paragraph::Paragraph(Paragraph const & par)
996 : itemdepth(par.itemdepth),
997 layout_(par.layout_),
998 text_(par.text_), begin_of_body_(par.begin_of_body_),
999 d(new Paragraph::Private(*par.d, this))
1004 Paragraph & Paragraph::operator=(Paragraph const & par)
1006 // needed as we will destroy the private part before copying it
1007 if (&par != this) {
1008 itemdepth = par.itemdepth;
1009 layout_ = par.layout();
1010 text_ = par.text_;
1011 begin_of_body_ = par.begin_of_body_;
1013 delete d;
1014 d = new Private(*par.d, this);
1016 return *this;
1020 Paragraph::~Paragraph()
1022 delete d;
1026 void Paragraph::write(Buffer const & buf, ostream & os,
1027 BufferParams const & bparams,
1028 depth_type & dth) const
1030 // The beginning or end of a deeper (i.e. nested) area?
1031 if (dth != params().depth()) {
1032 if (params().depth() > dth) {
1033 while (params().depth() > dth) {
1034 os << "\n\\begin_deeper";
1035 ++dth;
1037 } else {
1038 while (params().depth() < dth) {
1039 os << "\n\\end_deeper";
1040 --dth;
1045 // First write the layout
1046 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1048 params().write(os);
1050 Font font1(Font::ALL_INHERIT, bparams.language);
1052 Change running_change = Change(Change::UNCHANGED);
1054 int column = 0;
1055 for (pos_type i = 0; i <= size(); ++i) {
1057 Change change = lookupChange(i);
1058 Changes::lyxMarkChange(os, column, running_change, change);
1059 running_change = change;
1061 if (i == size())
1062 break;
1064 // Write font changes
1065 Font font2 = getFontSettings(bparams, i);
1066 if (font2 != font1) {
1067 font2.lyxWriteChanges(font1, os);
1068 column = 0;
1069 font1 = font2;
1072 value_type const c = getChar(i);
1073 switch (c) {
1074 case META_INSET:
1076 Inset const * inset = getInset(i);
1077 if (inset)
1078 if (inset->directWrite()) {
1079 // international char, let it write
1080 // code directly so it's shorter in
1081 // the file
1082 inset->write(buf, os);
1083 } else {
1084 if (i)
1085 os << '\n';
1086 os << "\\begin_inset ";
1087 inset->write(buf, os);
1088 os << "\n\\end_inset\n\n";
1089 column = 0;
1092 break;
1093 case '\\':
1094 os << "\n\\backslash\n";
1095 column = 0;
1096 break;
1097 case '.':
1098 if (i + 1 < size() && getChar(i + 1) == ' ') {
1099 os << ".\n";
1100 column = 0;
1101 } else
1102 os << '.';
1103 break;
1104 default:
1105 if ((column > 70 && c == ' ')
1106 || column > 79) {
1107 os << '\n';
1108 column = 0;
1110 // this check is to amend a bug. LyX sometimes
1111 // inserts '\0' this could cause problems.
1112 if (c != '\0') {
1113 std::vector<char> tmp = ucs4_to_utf8(c);
1114 tmp.push_back('\0');
1115 os << &tmp[0];
1116 } else
1117 lyxerr << "ERROR (Paragraph::writeFile):"
1118 " NULL char in structure." << endl;
1119 ++column;
1120 break;
1124 os << "\n\\end_layout\n";
1128 void Paragraph::validate(LaTeXFeatures & features) const
1130 d->validate(features, *layout());
1134 void Paragraph::insert(pos_type start, docstring const & str,
1135 Font const & font, Change const & change)
1137 for (size_t i = 0, n = str.size(); i != n ; ++i)
1138 insertChar(start + i, str[i], font, change);
1142 void Paragraph::appendChar(value_type c, Font const & font,
1143 Change const & change)
1145 // track change
1146 d->changes_.insert(change, text_.size());
1147 // when appending characters, no need to update tables
1148 text_.push_back(c);
1149 setFont(text_.size() - 1, font);
1153 void Paragraph::appendString(docstring const & s, Font const & font,
1154 Change const & change)
1156 size_t end = s.size();
1157 size_t oldsize = text_.size();
1158 size_t newsize = oldsize + end;
1159 size_t capacity = text_.capacity();
1160 if (newsize >= capacity)
1161 text_.reserve(std::max(capacity + 100, newsize));
1163 // FIXME: Optimize this!
1164 for (pos_type i = 0; i != end; ++i) {
1165 // track change
1166 d->changes_.insert(change, i);
1167 // when appending characters, no need to update tables
1168 text_.push_back(s[i]);
1170 d->fontlist_.setRange(oldsize, newsize, font);
1174 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1175 bool trackChanges)
1177 d->insertChar(pos, c, Change(trackChanges ?
1178 Change::INSERTED : Change::UNCHANGED));
1182 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1183 Font const & font, bool trackChanges)
1185 d->insertChar(pos, c, Change(trackChanges ?
1186 Change::INSERTED : Change::UNCHANGED));
1187 setFont(pos, font);
1191 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1192 Font const & font, Change const & change)
1194 d->insertChar(pos, c, change);
1195 setFont(pos, font);
1199 void Paragraph::insertInset(pos_type pos, Inset * inset,
1200 Font const & font, Change const & change)
1202 insertInset(pos, inset, change);
1203 // Set the font/language of the inset...
1204 setFont(pos, font);
1208 bool Paragraph::insetAllowed(InsetCode code)
1210 return !d->inset_owner_ || d->inset_owner_->insetAllowed(code);
1214 // Gets uninstantiated font setting at position.
1215 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1216 pos_type pos) const
1218 if (pos > size()) {
1219 lyxerr << " pos: " << pos << " size: " << size() << endl;
1220 BOOST_ASSERT(pos <= size());
1223 FontList::const_iterator cit = d->fontlist_.fontIterator(pos);
1224 if (cit != d->fontlist_.end())
1225 return cit->font();
1227 if (pos == size() && !empty())
1228 return getFontSettings(bparams, pos - 1);
1230 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1234 FontSpan Paragraph::fontSpan(pos_type pos) const
1236 BOOST_ASSERT(pos <= size());
1237 pos_type start = 0;
1239 FontList::const_iterator cit = d->fontlist_.begin();
1240 FontList::const_iterator end = d->fontlist_.end();
1241 for (; cit != end; ++cit) {
1242 if (cit->pos() >= pos) {
1243 if (pos >= beginOfBody())
1244 return FontSpan(std::max(start, beginOfBody()),
1245 cit->pos());
1246 else
1247 return FontSpan(start,
1248 std::min(beginOfBody() - 1,
1249 cit->pos()));
1251 start = cit->pos() + 1;
1254 // This should not happen, but if so, we take no chances.
1255 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1256 // << endl;
1257 return FontSpan(pos, pos);
1261 // Gets uninstantiated font setting at position 0
1262 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1264 if (!empty() && !d->fontlist_.empty())
1265 return d->fontlist_.begin()->font();
1267 return Font(Font::ALL_INHERIT, bparams.language);
1271 // Gets the fully instantiated font at a given position in a paragraph
1272 // This is basically the same function as Text::GetFont() in text2.cpp.
1273 // The difference is that this one is used for generating the LaTeX file,
1274 // and thus cosmetic "improvements" are disallowed: This has to deliver
1275 // the true picture of the buffer. (Asger)
1276 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1277 Font const & outerfont) const
1279 BOOST_ASSERT(pos >= 0);
1281 Font font = getFontSettings(bparams, pos);
1283 pos_type const body_pos = beginOfBody();
1284 if (pos < body_pos)
1285 font.realize(layout_->labelfont);
1286 else
1287 font.realize(layout_->font);
1289 font.realize(outerfont);
1290 font.realize(bparams.getFont());
1292 return font;
1296 Font const Paragraph::getLabelFont
1297 (BufferParams const & bparams, Font const & outerfont) const
1299 Font tmpfont = layout()->labelfont;
1300 tmpfont.setLanguage(getParLanguage(bparams));
1301 tmpfont.realize(outerfont);
1302 tmpfont.realize(bparams.getFont());
1303 return tmpfont;
1307 Font const Paragraph::getLayoutFont
1308 (BufferParams const & bparams, Font const & outerfont) const
1310 Font tmpfont = layout()->font;
1311 tmpfont.setLanguage(getParLanguage(bparams));
1312 tmpfont.realize(outerfont);
1313 tmpfont.realize(bparams.getFont());
1314 return tmpfont;
1318 /// Returns the height of the highest font in range
1319 Font_size Paragraph::highestFontInRange
1320 (pos_type startpos, pos_type endpos, Font_size def_size) const
1322 return d->fontlist_.highestInRange(startpos, endpos, def_size);
1326 Paragraph::value_type
1327 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1329 value_type c = getChar(pos);
1330 if (!lyxrc.rtl_support)
1331 return c;
1333 value_type uc = c;
1334 switch (c) {
1335 case '(':
1336 uc = ')';
1337 break;
1338 case ')':
1339 uc = '(';
1340 break;
1341 case '[':
1342 uc = ']';
1343 break;
1344 case ']':
1345 uc = '[';
1346 break;
1347 case '{':
1348 uc = '}';
1349 break;
1350 case '}':
1351 uc = '{';
1352 break;
1353 case '<':
1354 uc = '>';
1355 break;
1356 case '>':
1357 uc = '<';
1358 break;
1360 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1361 return uc;
1362 else
1363 return c;
1367 void Paragraph::setFont(pos_type pos, Font const & font)
1369 BOOST_ASSERT(pos <= size());
1371 // First, reduce font against layout/label font
1372 // Update: The setCharFont() routine in text2.cpp already
1373 // reduces font, so we don't need to do that here. (Asger)
1375 d->fontlist_.set(pos, font);
1379 void Paragraph::makeSameLayout(Paragraph const & par)
1381 layout(par.layout());
1382 // move to pimpl?
1383 d->params_ = par.params();
1387 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1389 if (isFreeSpacing())
1390 return false;
1392 int pos = 0;
1393 int count = 0;
1395 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1396 if (eraseChar(pos, trackChanges))
1397 ++count;
1398 else
1399 ++pos;
1402 return count > 0 || pos > 0;
1406 bool Paragraph::hasSameLayout(Paragraph const & par) const
1408 return par.layout() == layout() && d->params_.sameLayout(par.params());
1412 depth_type Paragraph::getDepth() const
1414 return params().depth();
1418 depth_type Paragraph::getMaxDepthAfter() const
1420 if (layout()->isEnvironment())
1421 return params().depth() + 1;
1422 else
1423 return params().depth();
1427 char Paragraph::getAlign() const
1429 if (params().align() == LYX_ALIGN_LAYOUT)
1430 return layout()->align;
1431 else
1432 return params().align();
1436 docstring const & Paragraph::getLabelstring() const
1438 return params().labelString();
1442 // the next two functions are for the manual labels
1443 docstring const Paragraph::getLabelWidthString() const
1445 if (layout()->margintype == MARGIN_MANUAL)
1446 return params().labelWidthString();
1447 else
1448 return _("Senseless with this layout!");
1452 void Paragraph::setLabelWidthString(docstring const & s)
1454 params().labelWidthString(s);
1458 docstring const Paragraph::translateIfPossible(docstring const & s,
1459 BufferParams const & bparams) const
1461 if (!support::isAscii(s) || s.empty()) {
1462 // This must be a user defined layout. We cannot translate
1463 // this, since gettext accepts only ascii keys.
1464 return s;
1466 // Probably standard layout, try to translate
1467 Messages & m = getMessages(getParLanguage(bparams)->code());
1468 return m.get(to_ascii(s));
1472 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1473 BufferParams const & bparams, bool process_appendix) const
1475 TextClass const & tclass = bparams.getTextClass();
1477 docstring fmt;
1478 if (process_appendix && params().appendix())
1479 fmt = translateIfPossible(layout->labelstring_appendix(),
1480 bparams);
1481 else
1482 fmt = translateIfPossible(layout->labelstring(), bparams);
1484 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1485 && !layout->counter.empty())
1486 fmt = "\\the" + layout->counter;
1488 // handle 'inherited level parts' in 'fmt',
1489 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1490 size_t const i = fmt.find('@', 0);
1491 if (i != docstring::npos) {
1492 size_t const j = fmt.find('@', i + 1);
1493 if (j != docstring::npos) {
1494 docstring parent(fmt, i + 1, j - i - 1);
1495 docstring label = from_ascii("??");
1496 if (tclass.hasLayout(parent))
1497 docstring label = expandLabel(tclass[parent], bparams,
1498 process_appendix);
1499 fmt = docstring(fmt, 0, i) + label
1500 + docstring(fmt, j + 1, docstring::npos);
1504 return tclass.counters().counterLabel(fmt);
1508 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1510 layout(new_layout);
1511 LyXAlignment const oldAlign = params().align();
1513 if (!(oldAlign & layout()->alignpossible)) {
1514 frontend::Alert::warning(_("Alignment not permitted"),
1515 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1516 params().align(LYX_ALIGN_LAYOUT);
1521 pos_type Paragraph::beginOfBody() const
1523 return begin_of_body_;
1527 void Paragraph::setBeginOfBody()
1529 if (layout()->labeltype != LABEL_MANUAL) {
1530 begin_of_body_ = 0;
1531 return;
1534 // Unroll the first two cycles of the loop
1535 // and remember the previous character to
1536 // remove unnecessary getChar() calls
1537 pos_type i = 0;
1538 pos_type end = size();
1539 if (i < end && !isNewline(i)) {
1540 ++i;
1541 char_type previous_char = 0;
1542 char_type temp = 0;
1543 if (i < end) {
1544 previous_char = text_[i];
1545 if (!isNewline(i)) {
1546 ++i;
1547 while (i < end && previous_char != ' ') {
1548 temp = text_[i];
1549 if (isNewline(i))
1550 break;
1551 ++i;
1552 previous_char = temp;
1558 begin_of_body_ = i;
1562 // returns -1 if inset not found
1563 int Paragraph::getPositionOfInset(Inset const * inset) const
1565 // Find the entry.
1566 InsetList::const_iterator it = d->insetlist_.begin();
1567 InsetList::const_iterator end = d->insetlist_.end();
1568 for (; it != end; ++it)
1569 if (it->inset == inset)
1570 return it->pos;
1571 return -1;
1575 InsetBibitem * Paragraph::bibitem() const
1577 if (!d->insetlist_.empty()) {
1578 Inset * inset = d->insetlist_.begin()->inset;
1579 if (inset->lyxCode() == BIBITEM_CODE)
1580 return static_cast<InsetBibitem *>(inset);
1582 return 0;
1586 bool Paragraph::forceDefaultParagraphs() const
1588 return inInset() && inInset()->forceDefaultParagraphs(0);
1592 namespace {
1594 // paragraphs inside floats need different alignment tags to avoid
1595 // unwanted space
1597 bool noTrivlistCentering(InsetCode code)
1599 return code == FLOAT_CODE || code == WRAP_CODE;
1603 string correction(string const & orig)
1605 if (orig == "flushleft")
1606 return "raggedright";
1607 if (orig == "flushright")
1608 return "raggedleft";
1609 if (orig == "center")
1610 return "centering";
1611 return orig;
1615 string const corrected_env(string const & suffix, string const & env,
1616 InsetCode code)
1618 string output = suffix + "{";
1619 if (noTrivlistCentering(code))
1620 output += correction(env);
1621 else
1622 output += env;
1623 output += "}";
1624 if (suffix == "\\begin")
1625 output += "\n";
1626 return output;
1630 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1632 if (!contains(str, "\n"))
1633 column += str.size();
1634 else {
1635 string tmp;
1636 texrow.newline();
1637 column = rsplit(str, tmp, '\n').size();
1641 } // namespace anon
1644 // This could go to ParagraphParameters if we want to
1645 int Paragraph::startTeXParParams(BufferParams const & bparams,
1646 odocstream & os, TexRow & texrow,
1647 bool moving_arg) const
1649 int column = 0;
1651 if (params().noindent()) {
1652 os << "\\noindent ";
1653 column += 10;
1656 LyXAlignment const curAlign = params().align();
1658 if (curAlign == layout()->align)
1659 return column;
1661 switch (curAlign) {
1662 case LYX_ALIGN_NONE:
1663 case LYX_ALIGN_BLOCK:
1664 case LYX_ALIGN_LAYOUT:
1665 case LYX_ALIGN_SPECIAL:
1666 break;
1667 case LYX_ALIGN_LEFT:
1668 case LYX_ALIGN_RIGHT:
1669 case LYX_ALIGN_CENTER:
1670 if (moving_arg) {
1671 os << "\\protect";
1672 column += 8;
1674 break;
1677 switch (curAlign) {
1678 case LYX_ALIGN_NONE:
1679 case LYX_ALIGN_BLOCK:
1680 case LYX_ALIGN_LAYOUT:
1681 case LYX_ALIGN_SPECIAL:
1682 break;
1683 case LYX_ALIGN_LEFT: {
1684 string output;
1685 if (getParLanguage(bparams)->babel() != "hebrew")
1686 output = corrected_env("\\begin", "flushleft", ownerCode());
1687 else
1688 output = corrected_env("\\begin", "flushright", ownerCode());
1689 os << from_ascii(output);
1690 adjust_row_column(output, texrow, column);
1691 break;
1692 } case LYX_ALIGN_RIGHT: {
1693 string output;
1694 if (getParLanguage(bparams)->babel() != "hebrew")
1695 output = corrected_env("\\begin", "flushright", ownerCode());
1696 else
1697 output = corrected_env("\\begin", "flushleft", ownerCode());
1698 os << from_ascii(output);
1699 adjust_row_column(output, texrow, column);
1700 break;
1701 } case LYX_ALIGN_CENTER: {
1702 string output;
1703 output = corrected_env("\\begin", "center", ownerCode());
1704 os << from_ascii(output);
1705 adjust_row_column(output, texrow, column);
1706 break;
1710 return column;
1714 // This could go to ParagraphParameters if we want to
1715 int Paragraph::endTeXParParams(BufferParams const & bparams,
1716 odocstream & os, TexRow & texrow,
1717 bool moving_arg) const
1719 int column = 0;
1721 switch (params().align()) {
1722 case LYX_ALIGN_NONE:
1723 case LYX_ALIGN_BLOCK:
1724 case LYX_ALIGN_LAYOUT:
1725 case LYX_ALIGN_SPECIAL:
1726 break;
1727 case LYX_ALIGN_LEFT:
1728 case LYX_ALIGN_RIGHT:
1729 case LYX_ALIGN_CENTER:
1730 if (moving_arg) {
1731 os << "\\protect";
1732 column = 8;
1734 break;
1737 switch (params().align()) {
1738 case LYX_ALIGN_NONE:
1739 case LYX_ALIGN_BLOCK:
1740 case LYX_ALIGN_LAYOUT:
1741 case LYX_ALIGN_SPECIAL:
1742 break;
1743 case LYX_ALIGN_LEFT: {
1744 string output;
1745 if (getParLanguage(bparams)->babel() != "hebrew")
1746 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1747 else
1748 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1749 os << from_ascii(output);
1750 adjust_row_column(output, texrow, column);
1751 break;
1752 } case LYX_ALIGN_RIGHT: {
1753 string output;
1754 if (getParLanguage(bparams)->babel() != "hebrew")
1755 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1756 else
1757 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1758 os << from_ascii(output);
1759 adjust_row_column(output, texrow, column);
1760 break;
1761 } case LYX_ALIGN_CENTER: {
1762 string output;
1763 output = corrected_env("\n\\par\\end", "center", ownerCode());
1764 os << from_ascii(output);
1765 adjust_row_column(output, texrow, column);
1766 break;
1770 return column;
1774 // This one spits out the text of the paragraph
1775 bool Paragraph::latex(Buffer const & buf,
1776 BufferParams const & bparams,
1777 Font const & outerfont,
1778 odocstream & os, TexRow & texrow,
1779 OutputParams const & runparams) const
1781 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1783 bool return_value = false;
1785 LayoutPtr style;
1787 // well we have to check if we are in an inset with unlimited
1788 // length (all in one row) if that is true then we don't allow
1789 // any special options in the paragraph and also we don't allow
1790 // any environment other than the default layout of the text class
1791 // to be valid!
1792 bool asdefault = forceDefaultParagraphs();
1794 if (asdefault) {
1795 style = bparams.getTextClass().defaultLayout();
1796 } else {
1797 style = layout();
1800 // Current base font for all inherited font changes, without any
1801 // change caused by an individual character, except for the language:
1802 // It is set to the language of the first character.
1803 // As long as we are in the label, this font is the base font of the
1804 // label. Before the first body character it is set to the base font
1805 // of the body.
1806 Font basefont;
1808 // Maybe we have to create a optional argument.
1809 pos_type body_pos = beginOfBody();
1810 unsigned int column = 0;
1812 if (body_pos > 0) {
1813 // the optional argument is kept in curly brackets in
1814 // case it contains a ']'
1815 os << "[{";
1816 column += 2;
1817 basefont = getLabelFont(bparams, outerfont);
1818 } else {
1819 basefont = getLayoutFont(bparams, outerfont);
1822 // Which font is currently active?
1823 Font running_font(basefont);
1824 // Do we have an open font change?
1825 bool open_font = false;
1827 Change runningChange = Change(Change::UNCHANGED);
1829 texrow.start(id(), 0);
1831 // if the paragraph is empty, the loop will not be entered at all
1832 if (empty()) {
1833 if (style->isCommand()) {
1834 os << '{';
1835 ++column;
1837 if (!asdefault)
1838 column += startTeXParParams(bparams, os, texrow,
1839 runparams.moving_arg);
1842 for (pos_type i = 0; i < size(); ++i) {
1843 // First char in paragraph or after label?
1844 if (i == body_pos) {
1845 if (body_pos > 0) {
1846 if (open_font) {
1847 column += running_font.latexWriteEndChanges(
1848 os, bparams, runparams,
1849 basefont, basefont);
1850 open_font = false;
1852 basefont = getLayoutFont(bparams, outerfont);
1853 running_font = basefont;
1855 column += Changes::latexMarkChange(os, bparams,
1856 runningChange, Change(Change::UNCHANGED));
1857 runningChange = Change(Change::UNCHANGED);
1859 os << "}] ";
1860 column +=3;
1862 if (style->isCommand()) {
1863 os << '{';
1864 ++column;
1867 if (!asdefault)
1868 column += startTeXParParams(bparams, os,
1869 texrow,
1870 runparams.moving_arg);
1873 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
1874 : lookupChange(i);
1876 if (bparams.outputChanges && runningChange != change) {
1877 if (open_font) {
1878 column += running_font.latexWriteEndChanges(
1879 os, bparams, runparams, basefont, basefont);
1880 open_font = false;
1882 basefont = getLayoutFont(bparams, outerfont);
1883 running_font = basefont;
1885 column += Changes::latexMarkChange(os, bparams, runningChange, change);
1886 runningChange = change;
1889 // do not output text which is marked deleted
1890 // if change tracking output is disabled
1891 if (!bparams.outputChanges && change.type == Change::DELETED) {
1892 continue;
1895 ++column;
1897 // Fully instantiated font
1898 Font const font = getFont(bparams, i, outerfont);
1900 Font const last_font = running_font;
1902 // Do we need to close the previous font?
1903 if (open_font &&
1904 (font != running_font ||
1905 font.language() != running_font.language()))
1907 column += running_font.latexWriteEndChanges(
1908 os, bparams, runparams, basefont,
1909 (i == body_pos-1) ? basefont : font);
1910 running_font = basefont;
1911 open_font = false;
1914 // Switch file encoding if necessary
1915 if (runparams.encoding->package() == Encoding::inputenc &&
1916 font.language()->encoding()->package() == Encoding::inputenc) {
1917 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
1918 runparams.moving_arg, *(runparams.encoding),
1919 *(font.language()->encoding()));
1920 if (enc_switch.first) {
1921 column += enc_switch.second;
1922 runparams.encoding = font.language()->encoding();
1926 value_type const c = getChar(i);
1928 // Do we need to change font?
1929 if ((font != running_font ||
1930 font.language() != running_font.language()) &&
1931 i != body_pos - 1)
1933 odocstringstream ods;
1934 column += font.latexWriteStartChanges(ods, bparams,
1935 runparams, basefont,
1936 last_font);
1937 running_font = font;
1938 open_font = true;
1939 docstring fontchange = ods.str();
1940 // check if the fontchange ends with a trailing blank
1941 // (like "\small " (see bug 3382)
1942 if (suffixIs(fontchange, ' ') && c == ' ')
1943 os << fontchange.substr(0, fontchange.size() - 1)
1944 << from_ascii("{}");
1945 else
1946 os << fontchange;
1949 if (c == ' ') {
1950 // FIXME: integrate this case in latexSpecialChar
1951 // Do not print the separation of the optional argument
1952 // if style->pass_thru is false. This works because
1953 // latexSpecialChar ignores spaces if
1954 // style->pass_thru is false.
1955 if (i != body_pos - 1) {
1956 if (d->simpleTeXBlanks(
1957 *(runparams.encoding), os, texrow,
1958 i, column, font, *style)) {
1959 // A surrogate pair was output. We
1960 // must not call latexSpecialChar
1961 // in this iteration, since it would output
1962 // the combining character again.
1963 ++i;
1964 continue;
1969 OutputParams rp = runparams;
1970 rp.free_spacing = style->free_spacing;
1971 rp.local_font = &font;
1972 rp.intitle = style->intitle;
1974 // Two major modes: LaTeX or plain
1975 // Handle here those cases common to both modes
1976 // and then split to handle the two modes separately.
1977 if (c == Paragraph::META_INSET)
1978 d->latexInset(buf, bparams, os,
1979 texrow, rp, running_font,
1980 basefont, outerfont, open_font,
1981 runningChange, *style, i, column);
1982 else
1983 d->latexSpecialChar(os, rp, running_font, runningChange,
1984 *style, i, column);
1986 // Set the encoding to that returned from simpleTeXSpecialChars (see
1987 // comment for encoding member in OutputParams.h)
1988 runparams.encoding = rp.encoding;
1991 // If we have an open font definition, we have to close it
1992 if (open_font) {
1993 #ifdef FIXED_LANGUAGE_END_DETECTION
1994 if (next_) {
1995 running_font
1996 .latexWriteEndChanges(os, bparams, runparams,
1997 basefont,
1998 next_->getFont(bparams, 0, outerfont));
1999 } else {
2000 running_font.latexWriteEndChanges(os, bparams,
2001 runparams, basefont, basefont);
2003 #else
2004 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2005 //FIXME: there as we start another \selectlanguage with the next paragraph if
2006 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2007 running_font.latexWriteEndChanges(os, bparams, runparams,
2008 basefont, basefont);
2009 #endif
2012 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2014 // Needed if there is an optional argument but no contents.
2015 if (body_pos > 0 && body_pos == size()) {
2016 os << "}]~";
2017 return_value = false;
2020 if (!asdefault) {
2021 column += endTeXParParams(bparams, os, texrow,
2022 runparams.moving_arg);
2025 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2026 return return_value;
2030 namespace {
2032 enum PAR_TAG {
2033 PAR_NONE=0,
2034 TT = 1,
2035 SF = 2,
2036 BF = 4,
2037 IT = 8,
2038 SL = 16,
2039 EM = 32
2043 string tag_name(PAR_TAG const & pt) {
2044 switch (pt) {
2045 case PAR_NONE: return "!-- --";
2046 case TT: return "tt";
2047 case SF: return "sf";
2048 case BF: return "bf";
2049 case IT: return "it";
2050 case SL: return "sl";
2051 case EM: return "em";
2053 return "";
2057 inline
2058 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2060 p1 = static_cast<PAR_TAG>(p1 | p2);
2064 inline
2065 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2067 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2070 } // anon
2073 bool Paragraph::emptyTag() const
2075 for (pos_type i = 0; i < size(); ++i) {
2076 if (isInset(i)) {
2077 Inset const * inset = getInset(i);
2078 InsetCode lyx_code = inset->lyxCode();
2079 if (lyx_code != TOC_CODE &&
2080 lyx_code != INCLUDE_CODE &&
2081 lyx_code != GRAPHICS_CODE &&
2082 lyx_code != ERT_CODE &&
2083 lyx_code != LISTINGS_CODE &&
2084 lyx_code != FLOAT_CODE &&
2085 lyx_code != TABULAR_CODE) {
2086 return false;
2088 } else {
2089 value_type c = getChar(i);
2090 if (c != ' ' && c != '\t')
2091 return false;
2094 return true;
2098 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2100 for (pos_type i = 0; i < size(); ++i) {
2101 if (isInset(i)) {
2102 Inset const * inset = getInset(i);
2103 InsetCode lyx_code = inset->lyxCode();
2104 if (lyx_code == LABEL_CODE) {
2105 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2106 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2111 return string();
2115 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2117 pos_type i;
2118 for (i = 0; i < size(); ++i) {
2119 if (isInset(i)) {
2120 Inset const * inset = getInset(i);
2121 inset->docbook(buf, os, runparams);
2122 } else {
2123 value_type c = getChar(i);
2124 if (c == ' ')
2125 break;
2126 os << sgml::escapeChar(c);
2129 return i;
2133 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2135 Font font_old;
2137 for (pos_type i = initial; i < size(); ++i) {
2138 Font font = getFont(buf.params(), i, outerfont);
2139 if (isInset(i))
2140 return false;
2141 if (i != initial && font != font_old)
2142 return false;
2143 font_old = font;
2146 return true;
2150 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2151 odocstream & os,
2152 OutputParams const & runparams,
2153 Font const & outerfont,
2154 pos_type initial) const
2156 bool emph_flag = false;
2158 LayoutPtr const & style = layout();
2159 Font font_old =
2160 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2162 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2163 os << "]]>";
2165 // parsing main loop
2166 for (pos_type i = initial; i < size(); ++i) {
2167 Font font = getFont(buf.params(), i, outerfont);
2169 // handle <emphasis> tag
2170 if (font_old.emph() != font.emph()) {
2171 if (font.emph() == Font::ON) {
2172 os << "<emphasis>";
2173 emph_flag = true;
2174 } else if (i != initial) {
2175 os << "</emphasis>";
2176 emph_flag = false;
2180 if (isInset(i)) {
2181 Inset const * inset = getInset(i);
2182 inset->docbook(buf, os, runparams);
2183 } else {
2184 value_type c = getChar(i);
2186 if (style->pass_thru)
2187 os.put(c);
2188 else
2189 os << sgml::escapeChar(c);
2191 font_old = font;
2194 if (emph_flag) {
2195 os << "</emphasis>";
2198 if (style->free_spacing)
2199 os << '\n';
2200 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2201 os << "<![CDATA[";
2205 bool Paragraph::isHfill(pos_type pos) const
2207 return isInset(pos)
2208 && getInset(pos)->lyxCode() == HFILL_CODE;
2212 bool Paragraph::isNewline(pos_type pos) const
2214 return isInset(pos)
2215 && getInset(pos)->lyxCode() == NEWLINE_CODE;
2219 bool Paragraph::isLineSeparator(pos_type pos) const
2221 value_type const c = getChar(pos);
2222 return isLineSeparatorChar(c)
2223 || (c == Paragraph::META_INSET && getInset(pos) &&
2224 getInset(pos)->isLineSeparator());
2228 /// Used by the spellchecker
2229 bool Paragraph::isLetter(pos_type pos) const
2231 if (isInset(pos))
2232 return getInset(pos)->isLetter();
2233 else {
2234 value_type const c = getChar(pos);
2235 return isLetterChar(c) || isDigit(c);
2240 Language const *
2241 Paragraph::getParLanguage(BufferParams const & bparams) const
2243 if (!empty())
2244 return getFirstFontSettings(bparams).language();
2245 // FIXME: we should check the prev par as well (Lgb)
2246 return bparams.language;
2250 bool Paragraph::isRTL(BufferParams const & bparams) const
2252 return lyxrc.rtl_support
2253 && getParLanguage(bparams)->rightToLeft()
2254 && ownerCode() != ERT_CODE
2255 && ownerCode() != LISTINGS_CODE;
2259 void Paragraph::changeLanguage(BufferParams const & bparams,
2260 Language const * from, Language const * to)
2262 // change language including dummy font change at the end
2263 for (pos_type i = 0; i <= size(); ++i) {
2264 Font font = getFontSettings(bparams, i);
2265 if (font.language() == from) {
2266 font.setLanguage(to);
2267 setFont(i, font);
2273 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2275 Language const * doc_language = bparams.language;
2276 FontList::const_iterator cit = d->fontlist_.begin();
2277 FontList::const_iterator end = d->fontlist_.end();
2279 for (; cit != end; ++cit)
2280 if (cit->font().language() != ignore_language &&
2281 cit->font().language() != latex_language &&
2282 cit->font().language() != doc_language)
2283 return true;
2284 return false;
2288 // Convert the paragraph to a string.
2289 // Used for building the table of contents
2290 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2292 return asString(buffer, 0, size(), label);
2296 docstring const Paragraph::asString(Buffer const & buffer,
2297 pos_type beg, pos_type end, bool label) const
2300 odocstringstream os;
2302 if (beg == 0 && label && !params().labelString().empty())
2303 os << params().labelString() << ' ';
2305 for (pos_type i = beg; i < end; ++i) {
2306 value_type const c = getChar(i);
2307 if (isPrintable(c))
2308 os.put(c);
2309 else if (c == META_INSET)
2310 getInset(i)->textString(buffer, os);
2313 return os.str();
2317 void Paragraph::setInsetOwner(Inset * inset)
2319 d->inset_owner_ = inset;
2323 int Paragraph::id() const
2325 return d->id_;
2329 LayoutPtr const & Paragraph::layout() const
2331 return layout_;
2335 void Paragraph::layout(LayoutPtr const & new_layout)
2337 layout_ = new_layout;
2341 Inset * Paragraph::inInset() const
2343 return d->inset_owner_;
2347 InsetCode Paragraph::ownerCode() const
2349 return d->inset_owner_ ?
2350 d->inset_owner_->lyxCode() : NO_CODE;
2354 ParagraphParameters & Paragraph::params()
2356 return d->params_;
2360 ParagraphParameters const & Paragraph::params() const
2362 return d->params_;
2366 bool Paragraph::isFreeSpacing() const
2368 if (layout()->free_spacing)
2369 return true;
2371 // for now we just need this, later should we need this in some
2372 // other way we can always add a function to Inset too.
2373 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2377 bool Paragraph::allowEmpty() const
2379 if (layout()->keepempty)
2380 return true;
2381 return ownerCode() == ERT_CODE || ownerCode() == LISTINGS_CODE;
2385 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2387 if (!Encodings::is_arabic(c))
2388 return c;
2390 value_type prev_char = ' ';
2391 value_type next_char = ' ';
2393 for (pos_type i = pos - 1; i >= 0; --i) {
2394 value_type const par_char = getChar(i);
2395 if (!Encodings::isComposeChar_arabic(par_char)) {
2396 prev_char = par_char;
2397 break;
2401 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2402 value_type const par_char = getChar(i);
2403 if (!Encodings::isComposeChar_arabic(par_char)) {
2404 next_char = par_char;
2405 break;
2409 if (Encodings::is_arabic(next_char)) {
2410 if (Encodings::is_arabic(prev_char) &&
2411 !Encodings::is_arabic_special(prev_char))
2412 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2413 else
2414 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2415 } else {
2416 if (Encodings::is_arabic(prev_char) &&
2417 !Encodings::is_arabic_special(prev_char))
2418 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2419 else
2420 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2425 int Paragraph::checkBiblio(bool track_changes)
2427 //FIXME From JS:
2428 //This is getting more and more a mess. ...We really should clean
2429 //up this bibitem issue for 1.6. See also bug 2743.
2431 // Add bibitem insets if necessary
2432 if (layout()->labeltype != LABEL_BIBLIO)
2433 return 0;
2435 bool hasbibitem = !d->insetlist_.empty()
2436 // Insist on it being in pos 0
2437 && getChar(0) == Paragraph::META_INSET
2438 && d->insetlist_.begin()->inset->lyxCode() == BIBITEM_CODE;
2440 docstring oldkey;
2441 docstring oldlabel;
2443 // remove a bibitem in pos != 0
2444 // restore it later in pos 0 if necessary
2445 // (e.g. if a user inserts contents _before_ the item)
2446 // we're assuming there's only one of these, which there
2447 // should be.
2448 int erasedInsetPosition = -1;
2449 InsetList::iterator it = d->insetlist_.begin();
2450 InsetList::iterator end = d->insetlist_.end();
2451 for (; it != end; ++it)
2452 if (it->inset->lyxCode() == BIBITEM_CODE
2453 && it->pos > 0) {
2454 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2455 oldkey = olditem->getParam("key");
2456 oldlabel = olditem->getParam("label");
2457 erasedInsetPosition = it->pos;
2458 eraseChar(erasedInsetPosition, track_changes);
2459 break;
2462 //There was an InsetBibitem at the beginning, and we didn't
2463 //have to erase one.
2464 if (hasbibitem && erasedInsetPosition < 0)
2465 return 0;
2467 //There was an InsetBibitem at the beginning and we did have to
2468 //erase one. So we give its properties to the beginning inset.
2469 if (hasbibitem) {
2470 InsetBibitem * inset =
2471 static_cast<InsetBibitem *>(d->insetlist_.begin()->inset);
2472 if (!oldkey.empty())
2473 inset->setParam("key", oldkey);
2474 inset->setParam("label", oldlabel);
2475 return -erasedInsetPosition;
2478 //There was no inset at the beginning, so we need to create one with
2479 //the key and label of the one we erased.
2480 InsetBibitem * inset(new InsetBibitem(InsetCommandParams(BIBITEM_CODE)));
2481 // restore values of previously deleted item in this par.
2482 if (!oldkey.empty())
2483 inset->setParam("key", oldkey);
2484 inset->setParam("label", oldlabel);
2485 insertInset(0, static_cast<Inset *>(inset),
2486 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2488 return 1;
2492 void Paragraph::checkAuthors(AuthorList const & authorList)
2494 d->changes_.checkAuthors(authorList);
2498 bool Paragraph::isUnchanged(pos_type pos) const
2500 return lookupChange(pos).type == Change::UNCHANGED;
2504 bool Paragraph::isInserted(pos_type pos) const
2506 return lookupChange(pos).type == Change::INSERTED;
2510 bool Paragraph::isDeleted(pos_type pos) const
2512 return lookupChange(pos).type == Change::DELETED;
2516 InsetList const & Paragraph::insetList() const
2518 return d->insetlist_;
2522 Inset * Paragraph::releaseInset(pos_type pos)
2524 Inset * inset = d->insetlist_.release(pos);
2525 /// does not honour change tracking!
2526 eraseChar(pos, false);
2527 return inset;
2531 Inset * Paragraph::getInset(pos_type pos)
2533 return d->insetlist_.get(pos);
2537 Inset const * Paragraph::getInset(pos_type pos) const
2539 return d->insetlist_.get(pos);
2543 int Paragraph::numberOfOptArgs() const
2545 int num = 0;
2546 InsetList::const_iterator it = insetList().begin();
2547 InsetList::const_iterator end = insetList().end();
2548 for (; it != end ; ++it) {
2549 if (it->inset->lyxCode() == OPTARG_CODE)
2550 ++num;
2552 return num;
2556 } // namespace lyx