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
11 * \author André Pönitz
13 * \author Jürgen Vigna
15 * Full author contact details are available in file CREDITS.
20 #include "Paragraph.h"
23 #include "BufferParams.h"
29 #include "LaTeXFeatures.h"
36 #include "OutputParams.h"
37 #include "output_latex.h"
38 #include "paragraph_funcs.h"
39 #include "ParagraphParameters.h"
44 #include "frontends/alert.h"
45 #include "frontends/FontMetrics.h"
47 #include "insets/InsetBibitem.h"
48 #include "insets/InsetOptArg.h"
50 #include "support/lstrings.h"
51 #include "support/textutils.h"
52 #include "support/convert.h"
53 #include "support/unicode.h"
55 #include <boost/bind.hpp>
56 #include <boost/next_prior.hpp>
68 using support::contains;
69 using support::suffixIs;
70 using support::rsplit;
73 /////////////////////////////////////////////////////////////////////
77 /////////////////////////////////////////////////////////////////////
83 class Paragraph::Pimpl {
86 Pimpl(Paragraph * owner);
87 /// "Copy constructor"
88 Pimpl(Pimpl const &, Paragraph * owner);
93 /// look up change at given pos
94 Change const & lookupChange(pos_type pos) const;
95 /// is there a change within the given range ?
96 bool isChanged(pos_type start, pos_type end) const;
97 /// will the paragraph be physically merged with the next
98 /// one if the imaginary end-of-par character is logically deleted?
99 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
100 /// set change for the entire par
101 void setChange(Change const & change);
102 /// set change at given pos
103 void setChange(pos_type pos, Change const & change);
104 /// accept changes within the given range
105 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
106 /// reject changes within the given range
107 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
110 value_type getChar(pos_type pos) const;
112 void insertChar(pos_type pos, value_type c, Change const & change);
114 void insertInset(pos_type pos, Inset * inset, Change const & change);
115 /// (logically) erase the char at pos; return true if it was actually erased
116 bool eraseChar(pos_type pos, bool trackChanges);
117 /// (logically) erase the given range; return the number of chars actually erased
118 int eraseChars(pos_type start, pos_type end, bool trackChanges);
122 /** A font entry covers a range of positions. Notice that the
123 entries in the list are inserted in random order.
124 I don't think it's worth the effort to implement a more effective
125 datastructure, because the number of different fonts in a paragraph
127 Nevertheless, I decided to store fontlist using a sorted vector:
128 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
129 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
130 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
131 (font_1 covers the chars 0,...,pos_1) (Dekel)
136 FontTable(pos_type p, Font const & f)
140 pos_type pos() const { return pos_; }
142 void pos(pos_type p) { pos_ = p; }
144 Font const & font() const { return font_; }
146 void font(Font const & f) { font_ = f;}
148 /// End position of paragraph this font attribute covers
150 /** Font. Interpretation of the font values:
151 If a value is Font::INHERIT_*, it means that the font
152 attribute is inherited from either the layout of this
153 paragraph or, in the case of nested paragraphs, from the
154 layout in the environment one level up until completely
156 The values Font::IGNORE_* and Font::TOGGLE are NOT
157 allowed in these font tables.
162 friend class matchFT;
166 /// used by lower_bound and upper_bound
167 int operator()(FontTable const & a, FontTable const & b) const {
168 return a.pos() < b.pos();
173 typedef std::vector<FontTable> FontList;
177 /// Output the surrogate pair formed by \p c and \p next to \p os.
178 /// \return the number of characters written.
179 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
181 /// Output a space in appropriate formatting (or a surrogate pair
182 /// if the next character is a combining character).
183 /// \return whether a surrogate pair was output.
184 bool simpleTeXBlanks(Encoding const &,
185 odocstream &, TexRow & texrow,
187 unsigned int & column,
189 Layout const & style);
191 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
193 TexRow & texrow, OutputParams &,
196 Font const & outerfont,
198 Change & running_change,
199 Layout const & style,
201 unsigned int & column, value_type const c);
204 void validate(LaTeXFeatures & features,
205 Layout const & layout) const;
210 static unsigned int paragraph_id;
212 ParagraphParameters params;
216 pos_type size() const { return owner_->size(); }
217 /// match a string against a particular point in the paragraph
218 bool isTextAt(std::string const & str, pos_type pos) const;
220 /// for recording and looking up changes
231 using std::upper_bound;
232 using std::lower_bound;
236 // Initialization of the counter for the paragraph id's,
237 unsigned int Paragraph::Pimpl::paragraph_id = 0;
241 struct special_phrase {
247 special_phrase const special_phrases[] = {
248 { "LyX", from_ascii("\\LyX{}"), false },
249 { "TeX", from_ascii("\\TeX{}"), true },
250 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
251 { "LaTeX", from_ascii("\\LaTeX{}"), true },
254 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
259 Paragraph::Pimpl::Pimpl(Paragraph * owner)
263 id_ = paragraph_id++;
267 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
268 : params(p.params), changes_(p.changes_), owner_(owner)
270 inset_owner = p.inset_owner;
271 fontlist = p.fontlist;
272 id_ = paragraph_id++;
276 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
278 BOOST_ASSERT(start >= 0 && start <= size());
279 BOOST_ASSERT(end > start && end <= size() + 1);
281 return changes_.isChanged(start, end);
285 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
286 // keep the logic here in sync with the logic of eraseChars()
292 Change change = changes_.lookup(size());
294 return change.type == Change::INSERTED && change.author == 0;
298 void Paragraph::Pimpl::setChange(Change const & change)
300 // beware of the imaginary end-of-par character!
301 changes_.set(change, 0, size() + 1);
304 * Propagate the change recursively - but not in case of DELETED!
306 * Imagine that your co-author makes changes in an existing inset. He
307 * sends your document to you and you come to the conclusion that the
308 * inset should go completely. If you erase it, LyX must not delete all
309 * text within the inset. Otherwise, the change tracked insertions of
310 * your co-author get lost and there is no way to restore them later.
312 * Conclusion: An inset's content should remain untouched if you delete it
315 if (change.type != Change::DELETED) {
316 for (pos_type pos = 0; pos < size(); ++pos) {
317 if (owner_->isInset(pos)) {
318 owner_->getInset(pos)->setChange(change);
325 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
327 BOOST_ASSERT(pos >= 0 && pos <= size());
329 changes_.set(change, pos);
331 // see comment in setChange(Change const &) above
333 if (change.type != Change::DELETED &&
334 pos < size() && owner_->isInset(pos)) {
335 owner_->getInset(pos)->setChange(change);
340 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
342 BOOST_ASSERT(pos >= 0 && pos <= size());
344 return changes_.lookup(pos);
348 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
350 BOOST_ASSERT(start >= 0 && start <= size());
351 BOOST_ASSERT(end > start && end <= size() + 1);
353 for (pos_type pos = start; pos < end; ++pos) {
354 switch (lookupChange(pos).type) {
355 case Change::UNCHANGED:
356 // accept changes in nested inset
357 if (pos < size() && owner_->isInset(pos)) {
358 owner_->getInset(pos)->acceptChanges(bparams);
363 case Change::INSERTED:
364 changes_.set(Change(Change::UNCHANGED), pos);
365 // also accept changes in nested inset
366 if (pos < size() && owner_->isInset(pos)) {
367 owner_->getInset(pos)->acceptChanges(bparams);
371 case Change::DELETED:
372 // Suppress access to non-existent
373 // "end-of-paragraph char"
375 eraseChar(pos, false);
386 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
388 BOOST_ASSERT(start >= 0 && start <= size());
389 BOOST_ASSERT(end > start && end <= size() + 1);
391 for (pos_type pos = start; pos < end; ++pos) {
392 switch (lookupChange(pos).type) {
393 case Change::UNCHANGED:
394 // reject changes in nested inset
395 if (pos < size() && owner_->isInset(pos)) {
396 owner_->getInset(pos)->rejectChanges(bparams);
400 case Change::INSERTED:
401 // Suppress access to non-existent
402 // "end-of-paragraph char"
404 eraseChar(pos, false);
410 case Change::DELETED:
411 changes_.set(Change(Change::UNCHANGED), pos);
413 // Do NOT reject changes within a deleted inset!
414 // There may be insertions of a co-author inside of it!
422 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
424 BOOST_ASSERT(pos >= 0 && pos <= size());
426 return owner_->getChar(pos);
430 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
432 BOOST_ASSERT(pos >= 0 && pos <= size());
435 changes_.insert(change, pos);
437 // This is actually very common when parsing buffers (and
438 // maybe inserting ascii text)
440 // when appending characters, no need to update tables
441 owner_->text_.push_back(c);
445 owner_->text_.insert(owner_->text_.begin() + pos, c);
447 // Update the font table.
448 FontTable search_font(pos, Font());
449 for (FontList::iterator it
450 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
451 it != fontlist.end(); ++it)
453 it->pos(it->pos() + 1);
457 owner_->insetlist.increasePosAfterPos(pos);
461 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
462 Change const & change)
465 BOOST_ASSERT(pos >= 0 && pos <= size());
467 insertChar(pos, META_INSET, change);
468 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
470 // Add a new entry in the insetlist.
471 owner_->insetlist.insert(inset, pos);
475 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
477 BOOST_ASSERT(pos >= 0 && pos <= size());
479 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
482 Change change = changes_.lookup(pos);
484 // set the character to DELETED if
485 // a) it was previously unchanged or
486 // b) it was inserted by a co-author
488 if (change.type == Change::UNCHANGED ||
489 (change.type == Change::INSERTED && change.author != 0)) {
490 setChange(pos, Change(Change::DELETED));
494 if (change.type == Change::DELETED)
498 // Don't physically access the imaginary end-of-paragraph character.
499 // eraseChar() can only mark it as DELETED. A physical deletion of
500 // end-of-par must be handled externally.
508 // if it is an inset, delete the inset entry
509 if (owner_->text_[pos] == Paragraph::META_INSET) {
510 owner_->insetlist.erase(pos);
513 owner_->text_.erase(owner_->text_.begin() + pos);
515 // Erase entries in the tables.
516 FontTable search_font(pos, Font());
518 FontList::iterator it =
519 lower_bound(fontlist.begin(),
521 search_font, matchFT());
522 if (it != fontlist.end() && it->pos() == pos &&
524 (it != fontlist.begin()
525 && boost::prior(it)->pos() == pos - 1))) {
526 // If it is a multi-character font
527 // entry, we just make it smaller
528 // (see update below), otherwise we
530 unsigned int const i = it - fontlist.begin();
531 fontlist.erase(fontlist.begin() + i);
532 it = fontlist.begin() + i;
533 if (i > 0 && i < fontlist.size() &&
534 fontlist[i - 1].font() == fontlist[i].font()) {
535 fontlist.erase(fontlist.begin() + i - 1);
536 it = fontlist.begin() + i - 1;
540 // Update all other entries
541 FontList::iterator fend = fontlist.end();
542 for (; it != fend; ++it)
543 it->pos(it->pos() - 1);
545 // Update the insetlist
546 owner_->insetlist.decreasePosAfterPos(pos);
552 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
554 BOOST_ASSERT(start >= 0 && start <= size());
555 BOOST_ASSERT(end >= start && end <= size() + 1);
558 for (pos_type count = end - start; count; --count) {
559 if (!eraseChar(i, trackChanges))
566 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
567 value_type next, Encoding const & encoding)
569 // Writing next here may circumvent a possible font change between
570 // c and next. Since next is only output if it forms a surrogate pair
571 // with c we can ignore this:
572 // A font change inside a surrogate pair does not make sense and is
573 // hopefully impossible to input.
574 // FIXME: change tracking
575 // Is this correct WRT change tracking?
576 docstring const latex1 = encoding.latexChar(next);
577 docstring const latex2 = encoding.latexChar(c);
578 os << latex1 << '{' << latex2 << '}';
579 return latex1.length() + latex2.length() + 2;
583 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
584 odocstream & os, TexRow & texrow,
586 unsigned int & column,
588 Layout const & style)
593 if (i < size() - 1) {
594 char_type next = getChar(i + 1);
595 if (Encodings::isCombiningChar(next)) {
596 // This space has an accent, so we must always output it.
597 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
603 if (lyxrc.plaintext_linelen > 0
604 && column > lyxrc.plaintext_linelen
606 && getChar(i - 1) != ' '
608 // same in FreeSpacing mode
609 && !owner_->isFreeSpacing()
610 // In typewriter mode, we want to avoid
611 // ! . ? : at the end of a line
612 && !(font.family() == Font::TYPEWRITER_FAMILY
613 && (getChar(i - 1) == '.'
614 || getChar(i - 1) == '?'
615 || getChar(i - 1) == ':'
616 || getChar(i - 1) == '!'))) {
619 texrow.start(owner_->id(), i + 1);
621 } else if (style.free_spacing) {
630 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
632 pos_type const len = str.length();
634 // is the paragraph large enough?
635 if (pos + len > size())
638 // does the wanted text start at point?
639 for (string::size_type i = 0; i < str.length(); ++i) {
640 // Caution: direct comparison of characters works only
641 // because str is pure ASCII.
642 if (str[i] != owner_->text_[pos + i])
646 // is there a font change in middle of the word?
647 FontList::const_iterator cit = fontlist.begin();
648 FontList::const_iterator end = fontlist.end();
649 for (; cit != end; ++cit) {
650 if (cit->pos() >= pos)
653 if (cit != end && pos + len - 1 > cit->pos())
660 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
661 BufferParams const & bparams,
664 OutputParams & runparams,
667 Font const & outerfont,
669 Change & running_change,
670 Layout const & style,
672 unsigned int & column,
675 if (style.pass_thru) {
676 if (c != Paragraph::META_INSET) {
678 // FIXME UNICODE: This can fail if c cannot
679 // be encoded in the current encoding.
682 owner_->getInset(i)->plaintext(buf, os, runparams);
686 // Two major modes: LaTeX or plain
687 // Handle here those cases common to both modes
688 // and then split to handle the two modes separately.
690 case Paragraph::META_INSET: {
691 Inset * inset = owner_->getInset(i);
693 // FIXME: remove this check
697 // FIXME: move this to InsetNewline::latex
698 if (inset->lyxCode() == Inset::NEWLINE_CODE) {
699 // newlines are handled differently here than
700 // the default in simpleTeXSpecialChars().
701 if (!style.newline_allowed) {
705 column += running_font.latexWriteEndChanges(
706 os, bparams, runparams,
711 if (running_font.family() == Font::TYPEWRITER_FAMILY)
714 basefont = owner_->getLayoutFont(bparams, outerfont);
715 running_font = basefont;
717 if (runparams.moving_arg)
723 texrow.start(owner_->id(), i + 1);
728 if (lookupChange(i).type == Change::DELETED) {
729 if( ++runparams.inDeletedInset == 1)
730 runparams.changeOfDeletedInset = lookupChange(i);
733 if (inset->canTrackChanges()) {
734 column += Changes::latexMarkChange(os, bparams, running_change,
735 Change(Change::UNCHANGED));
736 running_change = Change(Change::UNCHANGED);
740 odocstream::pos_type const len = os.tellp();
742 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
743 || inset->lyxCode() == Inset::MATH_CODE
744 || inset->lyxCode() == Inset::URL_CODE)
745 && running_font.isRightToLeft()) {
746 if (running_font.language()->lang() == "farsi")
753 // FIXME: Bug: we can have an empty font change here!
754 // if there has just been a font change, we are going to close it
755 // right now, which means stupid latex code like \textsf{}. AFAIK,
756 // this does not harm dvi output. A minor bug, thus (JMarc)
757 // Some insets cannot be inside a font change command.
758 // However, even such insets *can* be placed in \L or \R
759 // or their equivalents (for RTL language switches), so we don't
760 // close the language in those cases.
761 // ArabTeX, though, cannot handle this special behavior, it seems.
762 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
763 running_font.language()->lang() == "arabic_arabtex";
764 if (open_font && inset->noFontChange()) {
765 bool closeLanguage = arabtex ||
766 basefont.isRightToLeft() == running_font.isRightToLeft();
767 unsigned int count = running_font.latexWriteEndChanges(
768 os, bparams, runparams,
769 basefont, basefont, closeLanguage);
771 // if any font properties were closed, update the running_font,
772 // making sure, however, to leave the language as it was
774 // FIXME: probably a better way to keep track of the old
775 // language, than copying the entire font?
776 Font const copy_font(running_font);
777 basefont = owner_->getLayoutFont(bparams, outerfont);
778 running_font = basefont;
780 running_font.setLanguage(copy_font.language());
781 // leave font open if language is still open
782 open_font = (running_font.language() == basefont.language());
784 runparams.local_font = &basefont;
788 int tmp = inset->latex(buf, os, runparams);
791 if (running_font.language()->lang() == "farsi")
798 for (int j = 0; j < tmp; ++j) {
801 texrow.start(owner_->id(), i + 1);
804 column += os.tellp() - len;
807 if (lookupChange(i).type == Change::DELETED) {
808 --runparams.inDeletedInset;
814 // And now for the special cases within each mode
818 os << "\\textbackslash{}";
822 case '|': case '<': case '>':
823 // In T1 encoding, these characters exist
824 if (lyxrc.fontenc == "T1") {
826 //... but we should avoid ligatures
827 if ((c == '>' || c == '<')
829 && getChar(i + 1) == c) {
830 //os << "\\textcompwordmark{}";
832 // Jean-Marc, have a look at
833 // this. I think this works
841 // Typewriter font also has them
842 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
846 // Otherwise, we use what LaTeX
850 os << "\\textless{}";
854 os << "\\textgreater{}";
864 case '-': // "--" in Typewriter mode -> "-{}-"
865 if (i <= size() - 2 &&
866 getChar(i + 1) == '-' &&
867 running_font.family() == Font::TYPEWRITER_FAMILY) {
876 os << "\\char`\\\"{}";
881 case '%': case '#': case '{':
889 os << "\\textasciitilde{}";
894 os << "\\textasciicircum{}";
899 // avoid being mistaken for optional arguments
907 // Blanks are printed before font switching.
908 // Sure? I am not! (try nice-latex)
909 // I am sure it's correct. LyX might be smarter
910 // in the future, but for now, nothing wrong is
916 // I assume this is hack treating typewriter as verbatim
917 // FIXME UNICODE: This can fail if c cannot be encoded
918 // in the current encoding.
919 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
928 // FIXME: if we have "LaTeX" with a font
929 // change in the middle (before the 'T', then
930 // the "TeX" part is still special cased.
931 // Really we should only operate this on
932 // "words" for some definition of word
936 for (; pnr < phrases_nr; ++pnr) {
937 if (isTextAt(special_phrases[pnr].phrase, i)) {
938 os << special_phrases[pnr].macro;
939 i += special_phrases[pnr].phrase.length() - 1;
940 column += special_phrases[pnr].macro.length() - 1;
945 if (pnr == phrases_nr && c != '\0') {
946 Encoding const & encoding = *(runparams.encoding);
947 if (i < size() - 1) {
948 char_type next = getChar(i + 1);
949 if (Encodings::isCombiningChar(next)) {
950 column += latexSurrogatePair(os, c, next, encoding) - 1;
955 docstring const latex = encoding.latexChar(c);
956 if (latex.length() > 1 &&
957 latex[latex.length() - 1] != '}') {
958 // Prevent eating of a following
959 // space or command corruption by
960 // following characters
961 column += latex.length() + 1;
964 column += latex.length() - 1;
974 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
975 Layout const & layout) const
977 BufferParams const & bparams = features.bufferParams();
980 if (!params.spacing().isDefault())
981 features.require("setspace");
984 features.useLayout(layout.name());
987 Language const * doc_language = bparams.language;
989 FontList::const_iterator fcit = fontlist.begin();
990 FontList::const_iterator fend = fontlist.end();
991 for (; fcit != fend; ++fcit) {
992 if (fcit->font().noun() == Font::ON) {
993 LYXERR(Debug::LATEX) << "font.noun: "
994 << fcit->font().noun()
996 features.require("noun");
997 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
998 << to_utf8(fcit->font().stateText(0))
1001 switch (fcit->font().color()) {
1003 case Color::inherit:
1005 // probably we should put here all interface colors used for
1006 // font displaying! For now I just add this ones I know of (Jug)
1011 features.require("color");
1012 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1013 << to_utf8(fcit->font().stateText(0))
1017 Language const * language = fcit->font().language();
1018 if (language->babel() != doc_language->babel() &&
1019 language != ignore_language &&
1020 language != latex_language)
1022 features.useLanguage(language);
1023 LYXERR(Debug::LATEX) << "Found language "
1024 << language->lang() << endl;
1028 if (!params.leftIndent().zero())
1029 features.require("ParagraphLeftIndent");
1032 InsetList::const_iterator icit = owner_->insetlist.begin();
1033 InsetList::const_iterator iend = owner_->insetlist.end();
1034 for (; icit != iend; ++icit) {
1036 icit->inset->validate(features);
1037 if (layout.needprotect &&
1038 icit->inset->lyxCode() == Inset::FOOT_CODE)
1039 features.require("NeedLyXFootnoteCode");
1043 // then the contents
1044 for (pos_type i = 0; i < size() ; ++i) {
1045 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1046 if (!special_phrases[pnr].builtin
1047 && isTextAt(special_phrases[pnr].phrase, i)) {
1048 features.require(special_phrases[pnr].phrase);
1052 Encodings::validate(getChar(i), features);
1060 /////////////////////////////////////////////////////////////////////
1064 /////////////////////////////////////////////////////////////////////
1068 Paragraph::Paragraph()
1069 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1076 Paragraph::Paragraph(Paragraph const & par)
1077 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1078 layout_(par.layout_),
1079 text_(par.text_), begin_of_body_(par.begin_of_body_),
1080 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1082 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1083 InsetList::iterator it = insetlist.begin();
1084 InsetList::iterator end = insetlist.end();
1085 for (; it != end; ++it)
1086 it->inset = it->inset->clone();
1090 Paragraph & Paragraph::operator=(Paragraph const & par)
1092 // needed as we will destroy the pimpl_ before copying it
1094 itemdepth = par.itemdepth;
1096 insetlist = par.insetlist;
1097 InsetList::iterator it = insetlist.begin();
1098 InsetList::iterator end = insetlist.end();
1099 for (; it != end; ++it)
1100 it->inset = it->inset->clone();
1102 layout_ = par.layout();
1104 begin_of_body_ = par.begin_of_body_;
1107 pimpl_ = new Pimpl(*par.pimpl_, this);
1113 Paragraph::~Paragraph()
1117 //lyxerr << "Paragraph::paragraph_id = "
1118 // << Paragraph::paragraph_id << endl;
1122 void Paragraph::write(Buffer const & buf, ostream & os,
1123 BufferParams const & bparams,
1124 depth_type & dth) const
1126 // The beginning or end of a deeper (i.e. nested) area?
1127 if (dth != params().depth()) {
1128 if (params().depth() > dth) {
1129 while (params().depth() > dth) {
1130 os << "\n\\begin_deeper";
1134 while (params().depth() < dth) {
1135 os << "\n\\end_deeper";
1141 // First write the layout
1142 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1146 Font font1(Font::ALL_INHERIT, bparams.language);
1148 Change running_change = Change(Change::UNCHANGED);
1151 for (pos_type i = 0; i <= size(); ++i) {
1153 Change change = pimpl_->lookupChange(i);
1154 Changes::lyxMarkChange(os, column, running_change, change);
1155 running_change = change;
1160 // Write font changes
1161 Font font2 = getFontSettings(bparams, i);
1162 if (font2 != font1) {
1163 font2.lyxWriteChanges(font1, os);
1168 value_type const c = getChar(i);
1172 Inset const * inset = getInset(i);
1174 if (inset->directWrite()) {
1175 // international char, let it write
1176 // code directly so it's shorter in
1178 inset->write(buf, os);
1182 os << "\\begin_inset ";
1183 inset->write(buf, os);
1184 os << "\n\\end_inset\n\n";
1190 os << "\n\\backslash\n";
1194 if (i + 1 < size() && getChar(i + 1) == ' ') {
1201 if ((column > 70 && c == ' ')
1206 // this check is to amend a bug. LyX sometimes
1207 // inserts '\0' this could cause problems.
1209 std::vector<char> tmp = ucs4_to_utf8(c);
1210 tmp.push_back('\0');
1213 lyxerr << "ERROR (Paragraph::writeFile):"
1214 " NULL char in structure." << endl;
1220 os << "\n\\end_layout\n";
1224 void Paragraph::validate(LaTeXFeatures & features) const
1226 pimpl_->validate(features, *layout());
1230 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1232 return pimpl_->eraseChar(pos, trackChanges);
1236 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1238 return pimpl_->eraseChars(start, end, trackChanges);
1242 void Paragraph::insert(pos_type start, docstring const & str,
1243 Font const & font, Change const & change)
1245 for (size_t i = 0, n = str.size(); i != n ; ++i)
1246 insertChar(start + i, str[i], font, change);
1250 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1253 pimpl_->insertChar(pos, c, Change(trackChanges ?
1254 Change::INSERTED : Change::UNCHANGED));
1258 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1259 Font const & font, bool trackChanges)
1261 pimpl_->insertChar(pos, c, Change(trackChanges ?
1262 Change::INSERTED : Change::UNCHANGED));
1267 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1268 Font const & font, Change const & change)
1270 pimpl_->insertChar(pos, c, change);
1275 void Paragraph::insertInset(pos_type pos, Inset * inset,
1276 Change const & change)
1278 pimpl_->insertInset(pos, inset, change);
1282 void Paragraph::insertInset(pos_type pos, Inset * inset,
1283 Font const & font, Change const & change)
1285 pimpl_->insertInset(pos, inset, change);
1286 // Set the font/language of the inset...
1291 bool Paragraph::insetAllowed(Inset_code code)
1293 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1297 // Gets uninstantiated font setting at position.
1298 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1302 lyxerr << " pos: " << pos << " size: " << size() << endl;
1303 BOOST_ASSERT(pos <= size());
1306 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1307 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1308 for (; cit != end; ++cit)
1309 if (cit->pos() >= pos)
1315 if (pos == size() && !empty())
1316 return getFontSettings(bparams, pos - 1);
1318 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1322 FontSpan Paragraph::fontSpan(pos_type pos) const
1324 BOOST_ASSERT(pos <= size());
1327 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1328 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1329 for (; cit != end; ++cit) {
1330 if (cit->pos() >= pos) {
1331 if (pos >= beginOfBody())
1332 return FontSpan(std::max(start, beginOfBody()),
1335 return FontSpan(start,
1336 std::min(beginOfBody() - 1,
1339 start = cit->pos() + 1;
1342 // This should not happen, but if so, we take no chances.
1343 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1345 return FontSpan(pos, pos);
1349 // Gets uninstantiated font setting at position 0
1350 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1352 if (!empty() && !pimpl_->fontlist.empty())
1353 return pimpl_->fontlist[0].font();
1355 return Font(Font::ALL_INHERIT, bparams.language);
1359 // Gets the fully instantiated font at a given position in a paragraph
1360 // This is basically the same function as Text::GetFont() in text2.cpp.
1361 // The difference is that this one is used for generating the LaTeX file,
1362 // and thus cosmetic "improvements" are disallowed: This has to deliver
1363 // the true picture of the buffer. (Asger)
1364 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1365 Font const & outerfont) const
1367 BOOST_ASSERT(pos >= 0);
1369 Font font = getFontSettings(bparams, pos);
1371 pos_type const body_pos = beginOfBody();
1373 font.realize(layout_->labelfont);
1375 font.realize(layout_->font);
1377 font.realize(outerfont);
1378 font.realize(bparams.getFont());
1384 Font const Paragraph::getLabelFont
1385 (BufferParams const & bparams, Font const & outerfont) const
1387 Font tmpfont = layout()->labelfont;
1388 tmpfont.setLanguage(getParLanguage(bparams));
1389 tmpfont.realize(outerfont);
1390 tmpfont.realize(bparams.getFont());
1395 Font const Paragraph::getLayoutFont
1396 (BufferParams const & bparams, Font const & outerfont) const
1398 Font tmpfont = layout()->font;
1399 tmpfont.setLanguage(getParLanguage(bparams));
1400 tmpfont.realize(outerfont);
1401 tmpfont.realize(bparams.getFont());
1406 /// Returns the height of the highest font in range
1407 Font_size Paragraph::highestFontInRange
1408 (pos_type startpos, pos_type endpos, Font_size def_size) const
1410 if (pimpl_->fontlist.empty())
1413 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1414 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1415 for (; end_it != end; ++end_it) {
1416 if (end_it->pos() >= endpos)
1423 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1424 for (; cit != end; ++cit) {
1425 if (cit->pos() >= startpos)
1429 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1430 for (; cit != end_it; ++cit) {
1431 Font::FONT_SIZE size = cit->font().size();
1432 if (size == Font::INHERIT_SIZE)
1434 if (size > maxsize && size <= Font::SIZE_HUGER)
1441 Paragraph::value_type
1442 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1444 value_type c = getChar(pos);
1445 if (!lyxrc.rtl_support)
1475 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1482 void Paragraph::setFont(pos_type pos, Font const & font)
1484 BOOST_ASSERT(pos <= size());
1486 // First, reduce font against layout/label font
1487 // Update: The setCharFont() routine in text2.cpp already
1488 // reduces font, so we don't need to do that here. (Asger)
1489 // No need to simplify this because it will disappear
1490 // in a new kernel. (Asger)
1491 // Next search font table
1493 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1494 Pimpl::FontList::iterator it = beg;
1495 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1496 for (; it != endit; ++it) {
1497 if (it->pos() >= pos)
1500 size_t const i = distance(beg, it);
1501 bool notfound = (it == endit);
1503 if (!notfound && pimpl_->fontlist[i].font() == font)
1506 bool begin = pos == 0 || notfound ||
1507 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1508 // Is position pos is a beginning of a font block?
1509 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1510 // Is position pos is the end of a font block?
1511 if (begin && end) { // A single char block
1512 if (i + 1 < pimpl_->fontlist.size() &&
1513 pimpl_->fontlist[i + 1].font() == font) {
1514 // Merge the singleton block with the next block
1515 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1516 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1517 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1518 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1519 // Merge the singleton block with the previous block
1520 pimpl_->fontlist[i - 1].pos(pos);
1521 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1523 pimpl_->fontlist[i].font(font);
1525 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1526 pimpl_->fontlist[i - 1].pos(pos);
1528 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1529 Pimpl::FontTable(pos, font));
1531 pimpl_->fontlist[i].pos(pos - 1);
1532 if (!(i + 1 < pimpl_->fontlist.size() &&
1533 pimpl_->fontlist[i + 1].font() == font))
1534 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1535 Pimpl::FontTable(pos, font));
1536 } else { // The general case. The block is splitted into 3 blocks
1537 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1538 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1539 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1540 Pimpl::FontTable(pos, font));
1545 void Paragraph::makeSameLayout(Paragraph const & par)
1547 layout(par.layout());
1549 params() = par.params();
1553 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1555 if (isFreeSpacing())
1561 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1562 if (eraseChar(pos, trackChanges))
1568 return count > 0 || pos > 0;
1572 bool Paragraph::hasSameLayout(Paragraph const & par) const
1574 return par.layout() == layout() && params().sameLayout(par.params());
1578 depth_type Paragraph::getDepth() const
1580 return params().depth();
1584 depth_type Paragraph::getMaxDepthAfter() const
1586 if (layout()->isEnvironment())
1587 return params().depth() + 1;
1589 return params().depth();
1593 char Paragraph::getAlign() const
1595 if (params().align() == LYX_ALIGN_LAYOUT)
1596 return layout()->align;
1598 return params().align();
1602 docstring const & Paragraph::getLabelstring() const
1604 return params().labelString();
1608 // the next two functions are for the manual labels
1609 docstring const Paragraph::getLabelWidthString() const
1611 if (layout()->margintype == MARGIN_MANUAL)
1612 return params().labelWidthString();
1614 return _("Senseless with this layout!");
1618 void Paragraph::setLabelWidthString(docstring const & s)
1620 params().labelWidthString(s);
1624 docstring const Paragraph::translateIfPossible(docstring const & s,
1625 BufferParams const & bparams) const
1627 if (!support::isAscii(s) || s.empty()) {
1628 // This must be a user defined layout. We cannot translate
1629 // this, since gettext accepts only ascii keys.
1632 // Probably standard layout, try to translate
1633 Messages & m = getMessages(getParLanguage(bparams)->code());
1634 return m.get(to_ascii(s));
1638 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1639 BufferParams const & bparams, bool process_appendix) const
1641 TextClass const & tclass = bparams.getTextClass();
1644 if (process_appendix && params().appendix())
1645 fmt = translateIfPossible(layout->labelstring_appendix(),
1648 fmt = translateIfPossible(layout->labelstring(), bparams);
1650 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1651 && !layout->counter.empty())
1652 fmt = "\\the" + layout->counter;
1654 // handle 'inherited level parts' in 'fmt',
1655 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1656 size_t const i = fmt.find('@', 0);
1657 if (i != docstring::npos) {
1658 size_t const j = fmt.find('@', i + 1);
1659 if (j != docstring::npos) {
1660 docstring parent(fmt, i + 1, j - i - 1);
1661 docstring label = from_ascii("XXXX");
1662 if (tclass.hasLayout(parent))
1663 docstring label = expandLabel(tclass[parent], bparams,
1665 fmt = docstring(fmt, 0, i) + label
1666 + docstring(fmt, j + 1, docstring::npos);
1670 return tclass.counters().counterLabel(fmt);
1674 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1677 LyXAlignment const oldAlign = params().align();
1679 if (!(oldAlign & layout()->alignpossible)) {
1680 frontend::Alert::warning(_("Alignment not permitted"),
1681 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1682 params().align(LYX_ALIGN_LAYOUT);
1687 pos_type Paragraph::beginOfBody() const
1689 return begin_of_body_;
1693 void Paragraph::setBeginOfBody()
1695 if (layout()->labeltype != LABEL_MANUAL) {
1700 // Unroll the first two cycles of the loop
1701 // and remember the previous character to
1702 // remove unnecessary getChar() calls
1704 pos_type end = size();
1705 if (i < end && !isNewline(i)) {
1707 char_type previous_char = 0;
1710 previous_char = text_[i];
1711 if (!isNewline(i)) {
1713 while (i < end && previous_char != ' ') {
1718 previous_char = temp;
1728 // returns -1 if inset not found
1729 int Paragraph::getPositionOfInset(Inset const * inset) const
1732 InsetList::const_iterator it = insetlist.begin();
1733 InsetList::const_iterator end = insetlist.end();
1734 for (; it != end; ++it)
1735 if (it->inset == inset)
1741 InsetBibitem * Paragraph::bibitem() const
1743 if (!insetlist.empty()) {
1744 Inset * inset = insetlist.begin()->inset;
1745 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1746 return static_cast<InsetBibitem *>(inset);
1752 bool Paragraph::forceDefaultParagraphs() const
1754 return inInset() && inInset()->forceDefaultParagraphs(0);
1760 // paragraphs inside floats need different alignment tags to avoid
1763 bool noTrivlistCentering(Inset::Code code)
1765 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1769 string correction(string const & orig)
1771 if (orig == "flushleft")
1772 return "raggedright";
1773 if (orig == "flushright")
1774 return "raggedleft";
1775 if (orig == "center")
1781 string const corrected_env(string const & suffix, string const & env,
1784 string output = suffix + "{";
1785 if (noTrivlistCentering(code))
1786 output += correction(env);
1790 if (suffix == "\\begin")
1796 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1798 if (!contains(str, "\n"))
1799 column += str.size();
1803 column = rsplit(str, tmp, '\n').size();
1810 // This could go to ParagraphParameters if we want to
1811 int Paragraph::startTeXParParams(BufferParams const & bparams,
1812 odocstream & os, TexRow & texrow,
1813 bool moving_arg) const
1817 if (params().noindent()) {
1818 os << "\\noindent ";
1822 LyXAlignment const curAlign = params().align();
1824 if (curAlign == layout()->align)
1828 case LYX_ALIGN_NONE:
1829 case LYX_ALIGN_BLOCK:
1830 case LYX_ALIGN_LAYOUT:
1831 case LYX_ALIGN_SPECIAL:
1833 case LYX_ALIGN_LEFT:
1834 case LYX_ALIGN_RIGHT:
1835 case LYX_ALIGN_CENTER:
1844 case LYX_ALIGN_NONE:
1845 case LYX_ALIGN_BLOCK:
1846 case LYX_ALIGN_LAYOUT:
1847 case LYX_ALIGN_SPECIAL:
1849 case LYX_ALIGN_LEFT: {
1851 if (getParLanguage(bparams)->babel() != "hebrew")
1852 output = corrected_env("\\begin", "flushleft", ownerCode());
1854 output = corrected_env("\\begin", "flushright", ownerCode());
1855 os << from_ascii(output);
1856 adjust_row_column(output, texrow, column);
1858 } case LYX_ALIGN_RIGHT: {
1860 if (getParLanguage(bparams)->babel() != "hebrew")
1861 output = corrected_env("\\begin", "flushright", ownerCode());
1863 output = corrected_env("\\begin", "flushleft", ownerCode());
1864 os << from_ascii(output);
1865 adjust_row_column(output, texrow, column);
1867 } case LYX_ALIGN_CENTER: {
1869 output = corrected_env("\\begin", "center", ownerCode());
1870 os << from_ascii(output);
1871 adjust_row_column(output, texrow, column);
1880 // This could go to ParagraphParameters if we want to
1881 int Paragraph::endTeXParParams(BufferParams const & bparams,
1882 odocstream & os, TexRow & texrow,
1883 bool moving_arg) const
1887 switch (params().align()) {
1888 case LYX_ALIGN_NONE:
1889 case LYX_ALIGN_BLOCK:
1890 case LYX_ALIGN_LAYOUT:
1891 case LYX_ALIGN_SPECIAL:
1893 case LYX_ALIGN_LEFT:
1894 case LYX_ALIGN_RIGHT:
1895 case LYX_ALIGN_CENTER:
1903 switch (params().align()) {
1904 case LYX_ALIGN_NONE:
1905 case LYX_ALIGN_BLOCK:
1906 case LYX_ALIGN_LAYOUT:
1907 case LYX_ALIGN_SPECIAL:
1909 case LYX_ALIGN_LEFT: {
1911 if (getParLanguage(bparams)->babel() != "hebrew")
1912 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1914 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1915 os << from_ascii(output);
1916 adjust_row_column(output, texrow, column);
1918 } case LYX_ALIGN_RIGHT: {
1920 if (getParLanguage(bparams)->babel() != "hebrew")
1921 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1923 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1924 os << from_ascii(output);
1925 adjust_row_column(output, texrow, column);
1927 } case LYX_ALIGN_CENTER: {
1929 output = corrected_env("\n\\par\\end", "center", ownerCode());
1930 os << from_ascii(output);
1931 adjust_row_column(output, texrow, column);
1940 // This one spits out the text of the paragraph
1941 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1942 BufferParams const & bparams,
1943 Font const & outerfont,
1944 odocstream & os, TexRow & texrow,
1945 OutputParams const & runparams) const
1947 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1949 bool return_value = false;
1953 // well we have to check if we are in an inset with unlimited
1954 // length (all in one row) if that is true then we don't allow
1955 // any special options in the paragraph and also we don't allow
1956 // any environment other than the default layout of the text class
1958 bool asdefault = forceDefaultParagraphs();
1961 style = bparams.getTextClass().defaultLayout();
1966 // Current base font for all inherited font changes, without any
1967 // change caused by an individual character, except for the language:
1968 // It is set to the language of the first character.
1969 // As long as we are in the label, this font is the base font of the
1970 // label. Before the first body character it is set to the base font
1974 // Maybe we have to create a optional argument.
1975 pos_type body_pos = beginOfBody();
1976 unsigned int column = 0;
1979 // the optional argument is kept in curly brackets in
1980 // case it contains a ']'
1983 basefont = getLabelFont(bparams, outerfont);
1985 basefont = getLayoutFont(bparams, outerfont);
1988 // Which font is currently active?
1989 Font running_font(basefont);
1990 // Do we have an open font change?
1991 bool open_font = false;
1993 Change runningChange = Change(Change::UNCHANGED);
1995 texrow.start(id(), 0);
1997 // if the paragraph is empty, the loop will not be entered at all
1999 if (style->isCommand()) {
2004 column += startTeXParParams(bparams, os, texrow,
2005 runparams.moving_arg);
2008 for (pos_type i = 0; i < size(); ++i) {
2009 // First char in paragraph or after label?
2010 if (i == body_pos) {
2013 column += running_font.latexWriteEndChanges(
2014 os, bparams, runparams,
2015 basefont, basefont);
2018 basefont = getLayoutFont(bparams, outerfont);
2019 running_font = basefont;
2021 column += Changes::latexMarkChange(os, bparams,
2022 runningChange, Change(Change::UNCHANGED));
2023 runningChange = Change(Change::UNCHANGED);
2028 if (style->isCommand()) {
2034 column += startTeXParParams(bparams, os,
2036 runparams.moving_arg);
2039 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2040 : pimpl_->lookupChange(i);
2042 if (bparams.outputChanges && runningChange != change) {
2044 column += running_font.latexWriteEndChanges(
2045 os, bparams, runparams, basefont, basefont);
2048 basefont = getLayoutFont(bparams, outerfont);
2049 running_font = basefont;
2051 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2052 runningChange = change;
2055 // do not output text which is marked deleted
2056 // if change tracking output is disabled
2057 if (!bparams.outputChanges && change.type == Change::DELETED) {
2063 value_type const c = getChar(i);
2065 // Fully instantiated font
2066 Font const font = getFont(bparams, i, outerfont);
2068 Font const last_font = running_font;
2070 // Do we need to close the previous font?
2072 (font != running_font ||
2073 font.language() != running_font.language()))
2075 column += running_font.latexWriteEndChanges(
2076 os, bparams, runparams, basefont,
2077 (i == body_pos-1) ? basefont : font);
2078 running_font = basefont;
2082 // Switch file encoding if necessary
2083 if (runparams.encoding->package() == Encoding::inputenc &&
2084 font.language()->encoding()->package() == Encoding::inputenc) {
2085 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2086 runparams.moving_arg, *(runparams.encoding),
2087 *(font.language()->encoding()));
2088 if (enc_switch.first) {
2089 column += enc_switch.second;
2090 runparams.encoding = font.language()->encoding();
2094 // Do we need to change font?
2095 if ((font != running_font ||
2096 font.language() != running_font.language()) &&
2099 odocstringstream ods;
2100 column += font.latexWriteStartChanges(ods, bparams,
2101 runparams, basefont,
2103 running_font = font;
2105 docstring fontchange = ods.str();
2106 // check if the fontchange ends with a trailing blank
2107 // (like "\small " (see bug 3382)
2108 if (suffixIs(fontchange, ' ') && c == ' ')
2109 os << fontchange.substr(0, fontchange.size() - 1)
2110 << from_ascii("{}");
2116 // Do not print the separation of the optional argument
2117 // if style->pass_thru is false. This works because
2118 // simpleTeXSpecialChars ignores spaces if
2119 // style->pass_thru is false.
2120 if (i != body_pos - 1) {
2121 if (pimpl_->simpleTeXBlanks(
2122 *(runparams.encoding), os, texrow,
2123 i, column, font, *style))
2124 // A surrogate pair was output. We
2125 // must not call simpleTeXSpecialChars
2126 // in this iteration, since
2127 // simpleTeXBlanks incremented i, and
2128 // simpleTeXSpecialChars would output
2129 // the combining character again.
2134 OutputParams rp = runparams;
2135 rp.free_spacing = style->free_spacing;
2136 rp.local_font = &font;
2137 rp.intitle = style->intitle;
2138 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2139 texrow, rp, running_font,
2140 basefont, outerfont, open_font,
2141 runningChange, *style, i, column, c);
2143 // Set the encoding to that returned from simpleTeXSpecialChars (see
2144 // comment for encoding member in OutputParams.h)
2145 runparams.encoding = rp.encoding;
2148 // If we have an open font definition, we have to close it
2150 #ifdef FIXED_LANGUAGE_END_DETECTION
2153 .latexWriteEndChanges(os, bparams, runparams,
2155 next_->getFont(bparams, 0, outerfont));
2157 running_font.latexWriteEndChanges(os, bparams,
2158 runparams, basefont, basefont);
2161 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2162 //FIXME: there as we start another \selectlanguage with the next paragraph if
2163 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2164 running_font.latexWriteEndChanges(os, bparams, runparams,
2165 basefont, basefont);
2169 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2171 // Needed if there is an optional argument but no contents.
2172 if (body_pos > 0 && body_pos == size()) {
2174 return_value = false;
2178 column += endTeXParParams(bparams, os, texrow,
2179 runparams.moving_arg);
2182 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2183 return return_value;
2200 string tag_name(PAR_TAG const & pt) {
2202 case PAR_NONE: return "!-- --";
2203 case TT: return "tt";
2204 case SF: return "sf";
2205 case BF: return "bf";
2206 case IT: return "it";
2207 case SL: return "sl";
2208 case EM: return "em";
2215 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2217 p1 = static_cast<PAR_TAG>(p1 | p2);
2222 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2224 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2230 bool Paragraph::emptyTag() const
2232 for (pos_type i = 0; i < size(); ++i) {
2234 Inset const * inset = getInset(i);
2235 Inset::Code lyx_code = inset->lyxCode();
2236 if (lyx_code != Inset::TOC_CODE &&
2237 lyx_code != Inset::INCLUDE_CODE &&
2238 lyx_code != Inset::GRAPHICS_CODE &&
2239 lyx_code != Inset::ERT_CODE &&
2240 lyx_code != Inset::LISTINGS_CODE &&
2241 lyx_code != Inset::FLOAT_CODE &&
2242 lyx_code != Inset::TABULAR_CODE) {
2246 value_type c = getChar(i);
2247 if (c != ' ' && c != '\t')
2255 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2257 for (pos_type i = 0; i < size(); ++i) {
2259 Inset const * inset = getInset(i);
2260 Inset::Code lyx_code = inset->lyxCode();
2261 if (lyx_code == Inset::LABEL_CODE) {
2262 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2263 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2272 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2275 for (i = 0; i < size(); ++i) {
2277 Inset const * inset = getInset(i);
2278 inset->docbook(buf, os, runparams);
2280 value_type c = getChar(i);
2283 os << sgml::escapeChar(c);
2290 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2294 for (pos_type i = initial; i < size(); ++i) {
2295 Font font = getFont(buf.params(), i, outerfont);
2298 if (i != initial && font != font_old)
2307 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2309 OutputParams const & runparams,
2310 Font const & outerfont,
2311 pos_type initial) const
2313 bool emph_flag = false;
2315 LayoutPtr const & style = layout();
2317 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2319 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2322 // parsing main loop
2323 for (pos_type i = initial; i < size(); ++i) {
2324 Font font = getFont(buf.params(), i, outerfont);
2326 // handle <emphasis> tag
2327 if (font_old.emph() != font.emph()) {
2328 if (font.emph() == Font::ON) {
2331 } else if (i != initial) {
2332 os << "</emphasis>";
2338 Inset const * inset = getInset(i);
2339 inset->docbook(buf, os, runparams);
2341 value_type c = getChar(i);
2343 if (style->pass_thru)
2346 os << sgml::escapeChar(c);
2352 os << "</emphasis>";
2355 if (style->free_spacing)
2357 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2362 bool Paragraph::isNewline(pos_type pos) const
2365 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2369 bool Paragraph::isLineSeparator(pos_type pos) const
2371 value_type const c = getChar(pos);
2372 return isLineSeparatorChar(c)
2373 || (c == Paragraph::META_INSET && getInset(pos) &&
2374 getInset(pos)->isLineSeparator());
2378 /// Used by the spellchecker
2379 bool Paragraph::isLetter(pos_type pos) const
2382 return getInset(pos)->isLetter();
2384 value_type const c = getChar(pos);
2385 return isLetterChar(c) || isDigit(c);
2391 Paragraph::getParLanguage(BufferParams const & bparams) const
2394 return getFirstFontSettings(bparams).language();
2395 // FIXME: we should check the prev par as well (Lgb)
2396 return bparams.language;
2400 bool Paragraph::isRTL(BufferParams const & bparams) const
2402 return lyxrc.rtl_support
2403 && getParLanguage(bparams)->rightToLeft()
2404 && ownerCode() != Inset::ERT_CODE
2405 && ownerCode() != Inset::LISTINGS_CODE;
2409 void Paragraph::changeLanguage(BufferParams const & bparams,
2410 Language const * from, Language const * to)
2412 // change language including dummy font change at the end
2413 for (pos_type i = 0; i <= size(); ++i) {
2414 Font font = getFontSettings(bparams, i);
2415 if (font.language() == from) {
2416 font.setLanguage(to);
2423 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2425 Language const * doc_language = bparams.language;
2426 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2427 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2429 for (; cit != end; ++cit)
2430 if (cit->font().language() != ignore_language &&
2431 cit->font().language() != latex_language &&
2432 cit->font().language() != doc_language)
2438 // Convert the paragraph to a string.
2439 // Used for building the table of contents
2440 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2442 return asString(buffer, 0, size(), label);
2446 docstring const Paragraph::asString(Buffer const & buffer,
2447 pos_type beg, pos_type end, bool label) const
2450 odocstringstream os;
2452 if (beg == 0 && label && !params().labelString().empty())
2453 os << params().labelString() << ' ';
2455 for (pos_type i = beg; i < end; ++i) {
2456 value_type const c = getChar(i);
2459 else if (c == META_INSET)
2460 getInset(i)->textString(buffer, os);
2467 void Paragraph::setInsetOwner(Inset * inset)
2469 pimpl_->inset_owner = inset;
2473 Change const & Paragraph::lookupChange(pos_type pos) const
2475 BOOST_ASSERT(pos <= size());
2476 return pimpl_->lookupChange(pos);
2480 bool Paragraph::isChanged(pos_type start, pos_type end) const
2482 return pimpl_->isChanged(start, end);
2486 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2488 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2492 void Paragraph::setChange(Change const & change)
2494 pimpl_->setChange(change);
2498 void Paragraph::setChange(pos_type pos, Change const & change)
2500 pimpl_->setChange(pos, change);
2504 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2506 return pimpl_->acceptChanges(bparams, start, end);
2510 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2512 return pimpl_->rejectChanges(bparams, start, end);
2516 int Paragraph::id() const
2522 LayoutPtr const & Paragraph::layout() const
2528 void Paragraph::layout(LayoutPtr const & new_layout)
2530 layout_ = new_layout;
2534 Inset * Paragraph::inInset() const
2536 return pimpl_->inset_owner;
2540 Inset::Code Paragraph::ownerCode() const
2542 return pimpl_->inset_owner
2543 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2547 ParagraphParameters & Paragraph::params()
2549 return pimpl_->params;
2553 ParagraphParameters const & Paragraph::params() const
2555 return pimpl_->params;
2559 bool Paragraph::isFreeSpacing() const
2561 if (layout()->free_spacing)
2564 // for now we just need this, later should we need this in some
2565 // other way we can always add a function to Inset too.
2566 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2570 bool Paragraph::allowEmpty() const
2572 if (layout()->keepempty)
2574 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2578 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2580 if (!Encodings::is_arabic(c))
2583 value_type prev_char = ' ';
2584 value_type next_char = ' ';
2586 for (pos_type i = pos - 1; i >= 0; --i) {
2587 value_type const par_char = getChar(i);
2588 if (!Encodings::isComposeChar_arabic(par_char)) {
2589 prev_char = par_char;
2594 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2595 value_type const par_char = getChar(i);
2596 if (!Encodings::isComposeChar_arabic(par_char)) {
2597 next_char = par_char;
2602 if (Encodings::is_arabic(next_char)) {
2603 if (Encodings::is_arabic(prev_char) &&
2604 !Encodings::is_arabic_special(prev_char))
2605 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2607 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2609 if (Encodings::is_arabic(prev_char) &&
2610 !Encodings::is_arabic_special(prev_char))
2611 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2613 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2618 int Paragraph::checkBiblio(bool track_changes)
2621 //This is getting more and more a mess. ...We really should clean
2622 //up this bibitem issue for 1.6. See also bug 2743.
2624 // Add bibitem insets if necessary
2625 if (layout()->labeltype != LABEL_BIBLIO)
2628 bool hasbibitem = !insetlist.empty()
2629 // Insist on it being in pos 0
2630 && getChar(0) == Paragraph::META_INSET
2631 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2636 // remove a bibitem in pos != 0
2637 // restore it later in pos 0 if necessary
2638 // (e.g. if a user inserts contents _before_ the item)
2639 // we're assuming there's only one of these, which there
2641 int erasedInsetPosition = -1;
2642 InsetList::iterator it = insetlist.begin();
2643 InsetList::iterator end = insetlist.end();
2644 for (; it != end; ++it)
2645 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2647 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2648 oldkey = olditem->getParam("key");
2649 oldlabel = olditem->getParam("label");
2650 erasedInsetPosition = it->pos;
2651 eraseChar(erasedInsetPosition, track_changes);
2655 //There was an InsetBibitem at the beginning, and we didn't
2656 //have to erase one.
2657 if (hasbibitem && erasedInsetPosition < 0)
2660 //There was an InsetBibitem at the beginning and we did have to
2661 //erase one. So we give its properties to the beginning inset.
2663 InsetBibitem * inset =
2664 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2665 if (!oldkey.empty())
2666 inset->setParam("key", oldkey);
2667 inset->setParam("label", oldlabel);
2668 return -erasedInsetPosition;
2671 //There was no inset at the beginning, so we need to create one with
2672 //the key and label of the one we erased.
2673 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2674 // restore values of previously deleted item in this par.
2675 if (!oldkey.empty())
2676 inset->setParam("key", oldkey);
2677 inset->setParam("label", oldlabel);
2678 insertInset(0, static_cast<Inset *>(inset),
2679 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2685 void Paragraph::checkAuthors(AuthorList const & authorList)
2687 pimpl_->changes_.checkAuthors(authorList);