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 = expandLabel(tclass[parent], bparams,
1663 fmt = docstring(fmt, 0, i) + label
1664 + docstring(fmt, j + 1, docstring::npos);
1668 return tclass.counters().counterLabel(fmt);
1672 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1675 LyXAlignment const oldAlign = params().align();
1677 if (!(oldAlign & layout()->alignpossible)) {
1678 frontend::Alert::warning(_("Alignment not permitted"),
1679 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1680 params().align(LYX_ALIGN_LAYOUT);
1685 pos_type Paragraph::beginOfBody() const
1687 return begin_of_body_;
1691 void Paragraph::setBeginOfBody()
1693 if (layout()->labeltype != LABEL_MANUAL) {
1698 // Unroll the first two cycles of the loop
1699 // and remember the previous character to
1700 // remove unnecessary getChar() calls
1702 pos_type end = size();
1703 if (i < end && !isNewline(i)) {
1705 char_type previous_char = 0;
1708 previous_char = text_[i];
1709 if (!isNewline(i)) {
1711 while (i < end && previous_char != ' ') {
1716 previous_char = temp;
1726 // returns -1 if inset not found
1727 int Paragraph::getPositionOfInset(Inset const * inset) const
1730 InsetList::const_iterator it = insetlist.begin();
1731 InsetList::const_iterator end = insetlist.end();
1732 for (; it != end; ++it)
1733 if (it->inset == inset)
1739 InsetBibitem * Paragraph::bibitem() const
1741 if (!insetlist.empty()) {
1742 Inset * inset = insetlist.begin()->inset;
1743 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1744 return static_cast<InsetBibitem *>(inset);
1750 bool Paragraph::forceDefaultParagraphs() const
1752 return inInset() && inInset()->forceDefaultParagraphs(0);
1758 // paragraphs inside floats need different alignment tags to avoid
1761 bool noTrivlistCentering(Inset::Code code)
1763 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1767 string correction(string const & orig)
1769 if (orig == "flushleft")
1770 return "raggedright";
1771 if (orig == "flushright")
1772 return "raggedleft";
1773 if (orig == "center")
1779 string const corrected_env(string const & suffix, string const & env,
1782 string output = suffix + "{";
1783 if (noTrivlistCentering(code))
1784 output += correction(env);
1788 if (suffix == "\\begin")
1794 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1796 if (!contains(str, "\n"))
1797 column += str.size();
1801 column = rsplit(str, tmp, '\n').size();
1808 // This could go to ParagraphParameters if we want to
1809 int Paragraph::startTeXParParams(BufferParams const & bparams,
1810 odocstream & os, TexRow & texrow,
1811 bool moving_arg) const
1815 if (params().noindent()) {
1816 os << "\\noindent ";
1820 LyXAlignment const curAlign = params().align();
1822 if (curAlign == layout()->align)
1826 case LYX_ALIGN_NONE:
1827 case LYX_ALIGN_BLOCK:
1828 case LYX_ALIGN_LAYOUT:
1829 case LYX_ALIGN_SPECIAL:
1831 case LYX_ALIGN_LEFT:
1832 case LYX_ALIGN_RIGHT:
1833 case LYX_ALIGN_CENTER:
1842 case LYX_ALIGN_NONE:
1843 case LYX_ALIGN_BLOCK:
1844 case LYX_ALIGN_LAYOUT:
1845 case LYX_ALIGN_SPECIAL:
1847 case LYX_ALIGN_LEFT: {
1849 if (getParLanguage(bparams)->babel() != "hebrew")
1850 output = corrected_env("\\begin", "flushleft", ownerCode());
1852 output = corrected_env("\\begin", "flushright", ownerCode());
1853 os << from_ascii(output);
1854 adjust_row_column(output, texrow, column);
1856 } case LYX_ALIGN_RIGHT: {
1858 if (getParLanguage(bparams)->babel() != "hebrew")
1859 output = corrected_env("\\begin", "flushright", ownerCode());
1861 output = corrected_env("\\begin", "flushleft", ownerCode());
1862 os << from_ascii(output);
1863 adjust_row_column(output, texrow, column);
1865 } case LYX_ALIGN_CENTER: {
1867 output = corrected_env("\\begin", "center", ownerCode());
1868 os << from_ascii(output);
1869 adjust_row_column(output, texrow, column);
1878 // This could go to ParagraphParameters if we want to
1879 int Paragraph::endTeXParParams(BufferParams const & bparams,
1880 odocstream & os, TexRow & texrow,
1881 bool moving_arg) const
1885 switch (params().align()) {
1886 case LYX_ALIGN_NONE:
1887 case LYX_ALIGN_BLOCK:
1888 case LYX_ALIGN_LAYOUT:
1889 case LYX_ALIGN_SPECIAL:
1891 case LYX_ALIGN_LEFT:
1892 case LYX_ALIGN_RIGHT:
1893 case LYX_ALIGN_CENTER:
1901 switch (params().align()) {
1902 case LYX_ALIGN_NONE:
1903 case LYX_ALIGN_BLOCK:
1904 case LYX_ALIGN_LAYOUT:
1905 case LYX_ALIGN_SPECIAL:
1907 case LYX_ALIGN_LEFT: {
1909 if (getParLanguage(bparams)->babel() != "hebrew")
1910 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1912 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1913 os << from_ascii(output);
1914 adjust_row_column(output, texrow, column);
1916 } case LYX_ALIGN_RIGHT: {
1918 if (getParLanguage(bparams)->babel() != "hebrew")
1919 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1921 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1922 os << from_ascii(output);
1923 adjust_row_column(output, texrow, column);
1925 } case LYX_ALIGN_CENTER: {
1927 output = corrected_env("\n\\par\\end", "center", ownerCode());
1928 os << from_ascii(output);
1929 adjust_row_column(output, texrow, column);
1938 // This one spits out the text of the paragraph
1939 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1940 BufferParams const & bparams,
1941 Font const & outerfont,
1942 odocstream & os, TexRow & texrow,
1943 OutputParams const & runparams) const
1945 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1947 bool return_value = false;
1951 // well we have to check if we are in an inset with unlimited
1952 // length (all in one row) if that is true then we don't allow
1953 // any special options in the paragraph and also we don't allow
1954 // any environment other than the default layout of the text class
1956 bool asdefault = forceDefaultParagraphs();
1959 style = bparams.getTextClass().defaultLayout();
1964 // Current base font for all inherited font changes, without any
1965 // change caused by an individual character, except for the language:
1966 // It is set to the language of the first character.
1967 // As long as we are in the label, this font is the base font of the
1968 // label. Before the first body character it is set to the base font
1972 // Maybe we have to create a optional argument.
1973 pos_type body_pos = beginOfBody();
1974 unsigned int column = 0;
1977 // the optional argument is kept in curly brackets in
1978 // case it contains a ']'
1981 basefont = getLabelFont(bparams, outerfont);
1983 basefont = getLayoutFont(bparams, outerfont);
1986 // Which font is currently active?
1987 Font running_font(basefont);
1988 // Do we have an open font change?
1989 bool open_font = false;
1991 Change runningChange = Change(Change::UNCHANGED);
1993 texrow.start(id(), 0);
1995 // if the paragraph is empty, the loop will not be entered at all
1997 if (style->isCommand()) {
2002 column += startTeXParParams(bparams, os, texrow,
2003 runparams.moving_arg);
2006 for (pos_type i = 0; i < size(); ++i) {
2007 // First char in paragraph or after label?
2008 if (i == body_pos) {
2011 column += running_font.latexWriteEndChanges(
2012 os, bparams, runparams,
2013 basefont, basefont);
2016 basefont = getLayoutFont(bparams, outerfont);
2017 running_font = basefont;
2019 column += Changes::latexMarkChange(os, bparams,
2020 runningChange, Change(Change::UNCHANGED));
2021 runningChange = Change(Change::UNCHANGED);
2026 if (style->isCommand()) {
2032 column += startTeXParParams(bparams, os,
2034 runparams.moving_arg);
2037 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2038 : pimpl_->lookupChange(i);
2040 if (bparams.outputChanges && runningChange != change) {
2042 column += running_font.latexWriteEndChanges(
2043 os, bparams, runparams, basefont, basefont);
2046 basefont = getLayoutFont(bparams, outerfont);
2047 running_font = basefont;
2049 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2050 runningChange = change;
2053 // do not output text which is marked deleted
2054 // if change tracking output is disabled
2055 if (!bparams.outputChanges && change.type == Change::DELETED) {
2061 value_type const c = getChar(i);
2063 // Fully instantiated font
2064 Font const font = getFont(bparams, i, outerfont);
2066 Font const last_font = running_font;
2068 // Do we need to close the previous font?
2070 (font != running_font ||
2071 font.language() != running_font.language()))
2073 column += running_font.latexWriteEndChanges(
2074 os, bparams, runparams, basefont,
2075 (i == body_pos-1) ? basefont : font);
2076 running_font = basefont;
2080 // Switch file encoding if necessary
2081 if (runparams.encoding->package() == Encoding::inputenc &&
2082 font.language()->encoding()->package() == Encoding::inputenc) {
2083 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2084 runparams.moving_arg, *(runparams.encoding),
2085 *(font.language()->encoding()));
2086 if (enc_switch.first) {
2087 column += enc_switch.second;
2088 runparams.encoding = font.language()->encoding();
2092 // Do we need to change font?
2093 if ((font != running_font ||
2094 font.language() != running_font.language()) &&
2097 odocstringstream ods;
2098 column += font.latexWriteStartChanges(ods, bparams,
2099 runparams, basefont,
2101 running_font = font;
2103 docstring fontchange = ods.str();
2104 // check if the fontchange ends with a trailing blank
2105 // (like "\small " (see bug 3382)
2106 if (suffixIs(fontchange, ' ') && c == ' ')
2107 os << fontchange.substr(0, fontchange.size() - 1)
2108 << from_ascii("{}");
2114 // Do not print the separation of the optional argument
2115 // if style->pass_thru is false. This works because
2116 // simpleTeXSpecialChars ignores spaces if
2117 // style->pass_thru is false.
2118 if (i != body_pos - 1) {
2119 if (pimpl_->simpleTeXBlanks(
2120 *(runparams.encoding), os, texrow,
2121 i, column, font, *style))
2122 // A surrogate pair was output. We
2123 // must not call simpleTeXSpecialChars
2124 // in this iteration, since
2125 // simpleTeXBlanks incremented i, and
2126 // simpleTeXSpecialChars would output
2127 // the combining character again.
2132 OutputParams rp = runparams;
2133 rp.free_spacing = style->free_spacing;
2134 rp.local_font = &font;
2135 rp.intitle = style->intitle;
2136 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2137 texrow, rp, running_font,
2138 basefont, outerfont, open_font,
2139 runningChange, *style, i, column, c);
2141 // Set the encoding to that returned from simpleTeXSpecialChars (see
2142 // comment for encoding member in OutputParams.h)
2143 runparams.encoding = rp.encoding;
2146 // If we have an open font definition, we have to close it
2148 #ifdef FIXED_LANGUAGE_END_DETECTION
2151 .latexWriteEndChanges(os, bparams, runparams,
2153 next_->getFont(bparams, 0, outerfont));
2155 running_font.latexWriteEndChanges(os, bparams,
2156 runparams, basefont, basefont);
2159 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2160 //FIXME: there as we start another \selectlanguage with the next paragraph if
2161 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2162 running_font.latexWriteEndChanges(os, bparams, runparams,
2163 basefont, basefont);
2167 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2169 // Needed if there is an optional argument but no contents.
2170 if (body_pos > 0 && body_pos == size()) {
2172 return_value = false;
2176 column += endTeXParParams(bparams, os, texrow,
2177 runparams.moving_arg);
2180 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2181 return return_value;
2198 string tag_name(PAR_TAG const & pt) {
2200 case PAR_NONE: return "!-- --";
2201 case TT: return "tt";
2202 case SF: return "sf";
2203 case BF: return "bf";
2204 case IT: return "it";
2205 case SL: return "sl";
2206 case EM: return "em";
2213 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2215 p1 = static_cast<PAR_TAG>(p1 | p2);
2220 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2222 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2228 bool Paragraph::emptyTag() const
2230 for (pos_type i = 0; i < size(); ++i) {
2232 Inset const * inset = getInset(i);
2233 Inset::Code lyx_code = inset->lyxCode();
2234 if (lyx_code != Inset::TOC_CODE &&
2235 lyx_code != Inset::INCLUDE_CODE &&
2236 lyx_code != Inset::GRAPHICS_CODE &&
2237 lyx_code != Inset::ERT_CODE &&
2238 lyx_code != Inset::LISTINGS_CODE &&
2239 lyx_code != Inset::FLOAT_CODE &&
2240 lyx_code != Inset::TABULAR_CODE) {
2244 value_type c = getChar(i);
2245 if (c != ' ' && c != '\t')
2253 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2255 for (pos_type i = 0; i < size(); ++i) {
2257 Inset const * inset = getInset(i);
2258 Inset::Code lyx_code = inset->lyxCode();
2259 if (lyx_code == Inset::LABEL_CODE) {
2260 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2261 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2270 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2273 for (i = 0; i < size(); ++i) {
2275 Inset const * inset = getInset(i);
2276 inset->docbook(buf, os, runparams);
2278 value_type c = getChar(i);
2281 os << sgml::escapeChar(c);
2288 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2292 for (pos_type i = initial; i < size(); ++i) {
2293 Font font = getFont(buf.params(), i, outerfont);
2296 if (i != initial && font != font_old)
2305 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2307 OutputParams const & runparams,
2308 Font const & outerfont,
2309 pos_type initial) const
2311 bool emph_flag = false;
2313 LayoutPtr const & style = layout();
2315 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2317 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2320 // parsing main loop
2321 for (pos_type i = initial; i < size(); ++i) {
2322 Font font = getFont(buf.params(), i, outerfont);
2324 // handle <emphasis> tag
2325 if (font_old.emph() != font.emph()) {
2326 if (font.emph() == Font::ON) {
2329 } else if (i != initial) {
2330 os << "</emphasis>";
2336 Inset const * inset = getInset(i);
2337 inset->docbook(buf, os, runparams);
2339 value_type c = getChar(i);
2341 if (style->pass_thru)
2344 os << sgml::escapeChar(c);
2350 os << "</emphasis>";
2353 if (style->free_spacing)
2355 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2360 bool Paragraph::isNewline(pos_type pos) const
2363 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2367 bool Paragraph::isLineSeparator(pos_type pos) const
2369 value_type const c = getChar(pos);
2370 return isLineSeparatorChar(c)
2371 || (c == Paragraph::META_INSET && getInset(pos) &&
2372 getInset(pos)->isLineSeparator());
2376 /// Used by the spellchecker
2377 bool Paragraph::isLetter(pos_type pos) const
2380 return getInset(pos)->isLetter();
2382 value_type const c = getChar(pos);
2383 return isLetterChar(c) || isDigit(c);
2389 Paragraph::getParLanguage(BufferParams const & bparams) const
2392 return getFirstFontSettings(bparams).language();
2393 // FIXME: we should check the prev par as well (Lgb)
2394 return bparams.language;
2398 bool Paragraph::isRTL(BufferParams const & bparams) const
2400 return lyxrc.rtl_support
2401 && getParLanguage(bparams)->rightToLeft()
2402 && ownerCode() != Inset::ERT_CODE
2403 && ownerCode() != Inset::LISTINGS_CODE;
2407 void Paragraph::changeLanguage(BufferParams const & bparams,
2408 Language const * from, Language const * to)
2410 // change language including dummy font change at the end
2411 for (pos_type i = 0; i <= size(); ++i) {
2412 Font font = getFontSettings(bparams, i);
2413 if (font.language() == from) {
2414 font.setLanguage(to);
2421 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2423 Language const * doc_language = bparams.language;
2424 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2425 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2427 for (; cit != end; ++cit)
2428 if (cit->font().language() != ignore_language &&
2429 cit->font().language() != latex_language &&
2430 cit->font().language() != doc_language)
2436 // Convert the paragraph to a string.
2437 // Used for building the table of contents
2438 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2440 return asString(buffer, 0, size(), label);
2444 docstring const Paragraph::asString(Buffer const & buffer,
2445 pos_type beg, pos_type end, bool label) const
2448 odocstringstream os;
2450 if (beg == 0 && label && !params().labelString().empty())
2451 os << params().labelString() << ' ';
2453 for (pos_type i = beg; i < end; ++i) {
2454 value_type const c = getChar(i);
2457 else if (c == META_INSET)
2458 getInset(i)->textString(buffer, os);
2465 void Paragraph::setInsetOwner(Inset * inset)
2467 pimpl_->inset_owner = inset;
2471 Change const & Paragraph::lookupChange(pos_type pos) const
2473 BOOST_ASSERT(pos <= size());
2474 return pimpl_->lookupChange(pos);
2478 bool Paragraph::isChanged(pos_type start, pos_type end) const
2480 return pimpl_->isChanged(start, end);
2484 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2486 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2490 void Paragraph::setChange(Change const & change)
2492 pimpl_->setChange(change);
2496 void Paragraph::setChange(pos_type pos, Change const & change)
2498 pimpl_->setChange(pos, change);
2502 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2504 return pimpl_->acceptChanges(bparams, start, end);
2508 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2510 return pimpl_->rejectChanges(bparams, start, end);
2514 int Paragraph::id() const
2520 LayoutPtr const & Paragraph::layout() const
2526 void Paragraph::layout(LayoutPtr const & new_layout)
2528 layout_ = new_layout;
2532 Inset * Paragraph::inInset() const
2534 return pimpl_->inset_owner;
2538 Inset::Code Paragraph::ownerCode() const
2540 return pimpl_->inset_owner
2541 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2545 ParagraphParameters & Paragraph::params()
2547 return pimpl_->params;
2551 ParagraphParameters const & Paragraph::params() const
2553 return pimpl_->params;
2557 bool Paragraph::isFreeSpacing() const
2559 if (layout()->free_spacing)
2562 // for now we just need this, later should we need this in some
2563 // other way we can always add a function to Inset too.
2564 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2568 bool Paragraph::allowEmpty() const
2570 if (layout()->keepempty)
2572 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2576 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2578 if (!Encodings::is_arabic(c))
2581 value_type prev_char = ' ';
2582 value_type next_char = ' ';
2584 for (pos_type i = pos - 1; i >= 0; --i) {
2585 value_type const par_char = getChar(i);
2586 if (!Encodings::isComposeChar_arabic(par_char)) {
2587 prev_char = par_char;
2592 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2593 value_type const par_char = getChar(i);
2594 if (!Encodings::isComposeChar_arabic(par_char)) {
2595 next_char = par_char;
2600 if (Encodings::is_arabic(next_char)) {
2601 if (Encodings::is_arabic(prev_char) &&
2602 !Encodings::is_arabic_special(prev_char))
2603 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2605 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2607 if (Encodings::is_arabic(prev_char) &&
2608 !Encodings::is_arabic_special(prev_char))
2609 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2611 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2616 int Paragraph::checkBiblio(bool track_changes)
2619 //This is getting more and more a mess. ...We really should clean
2620 //up this bibitem issue for 1.6. See also bug 2743.
2622 // Add bibitem insets if necessary
2623 if (layout()->labeltype != LABEL_BIBLIO)
2626 bool hasbibitem = !insetlist.empty()
2627 // Insist on it being in pos 0
2628 && getChar(0) == Paragraph::META_INSET
2629 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2634 // remove a bibitem in pos != 0
2635 // restore it later in pos 0 if necessary
2636 // (e.g. if a user inserts contents _before_ the item)
2637 // we're assuming there's only one of these, which there
2639 int erasedInsetPosition = -1;
2640 InsetList::iterator it = insetlist.begin();
2641 InsetList::iterator end = insetlist.end();
2642 for (; it != end; ++it)
2643 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2645 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2646 oldkey = olditem->getParam("key");
2647 oldlabel = olditem->getParam("label");
2648 erasedInsetPosition = it->pos;
2649 eraseChar(erasedInsetPosition, track_changes);
2653 //There was an InsetBibitem at the beginning, and we didn't
2654 //have to erase one.
2655 if (hasbibitem && erasedInsetPosition < 0)
2658 //There was an InsetBibitem at the beginning and we did have to
2659 //erase one. So we give its properties to the beginning inset.
2661 InsetBibitem * inset =
2662 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2663 if (!oldkey.empty())
2664 inset->setParam("key", oldkey);
2665 inset->setParam("label", oldlabel);
2666 return -erasedInsetPosition;
2669 //There was no inset at the beginning, so we need to create one with
2670 //the key and label of the one we erased.
2671 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2672 // restore values of previously deleted item in this par.
2673 if (!oldkey.empty())
2674 inset->setParam("key", oldkey);
2675 inset->setParam("label", oldlabel);
2676 insertInset(0, static_cast<Inset *>(inset),
2677 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2683 void Paragraph::checkAuthors(AuthorList const & authorList)
2685 pimpl_->changes_.checkAuthors(authorList);