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"
40 #include "rowpainter.h"
45 #include "frontends/alert.h"
46 #include "frontends/FontMetrics.h"
48 #include "insets/InsetBibitem.h"
49 #include "insets/InsetOptArg.h"
51 #include "support/lstrings.h"
52 #include "support/textutils.h"
53 #include "support/convert.h"
54 #include "support/unicode.h"
56 #include <boost/bind.hpp>
57 #include <boost/next_prior.hpp>
69 using support::contains;
70 using support::suffixIs;
71 using support::rsplit;
74 /////////////////////////////////////////////////////////////////////
78 /////////////////////////////////////////////////////////////////////
84 class Paragraph::Pimpl {
87 Pimpl(Paragraph * owner);
88 /// "Copy constructor"
89 Pimpl(Pimpl const &, Paragraph * owner);
94 /// look up change at given pos
95 Change const & lookupChange(pos_type pos) const;
96 /// is there a change within the given range ?
97 bool isChanged(pos_type start, pos_type end) const;
98 /// will the paragraph be physically merged with the next
99 /// one if the imaginary end-of-par character is logically deleted?
100 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
101 /// set change for the entire par
102 void setChange(Change const & change);
103 /// set change at given pos
104 void setChange(pos_type pos, Change const & change);
105 /// accept changes within the given range
106 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
107 /// reject changes within the given range
108 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
111 value_type getChar(pos_type pos) const;
113 void insertChar(pos_type pos, value_type c, Change const & change);
115 void insertInset(pos_type pos, Inset * inset, Change const & change);
116 /// (logically) erase the char at pos; return true if it was actually erased
117 bool eraseChar(pos_type pos, bool trackChanges);
118 /// (logically) erase the given range; return the number of chars actually erased
119 int eraseChars(pos_type start, pos_type end, bool trackChanges);
123 /** A font entry covers a range of positions. Notice that the
124 entries in the list are inserted in random order.
125 I don't think it's worth the effort to implement a more effective
126 datastructure, because the number of different fonts in a paragraph
128 Nevertheless, I decided to store fontlist using a sorted vector:
129 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
130 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
131 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
132 (font_1 covers the chars 0,...,pos_1) (Dekel)
137 FontTable(pos_type p, Font const & f)
141 pos_type pos() const { return pos_; }
143 void pos(pos_type p) { pos_ = p; }
145 Font const & font() const { return font_; }
147 void font(Font const & f) { font_ = f;}
149 /// End position of paragraph this font attribute covers
151 /** Font. Interpretation of the font values:
152 If a value is Font::INHERIT_*, it means that the font
153 attribute is inherited from either the layout of this
154 paragraph or, in the case of nested paragraphs, from the
155 layout in the environment one level up until completely
157 The values Font::IGNORE_* and Font::TOGGLE are NOT
158 allowed in these font tables.
163 friend class matchFT;
167 /// used by lower_bound and upper_bound
168 int operator()(FontTable const & a, FontTable const & b) const {
169 return a.pos() < b.pos();
174 typedef std::vector<FontTable> FontList;
178 /// Output the surrogate pair formed by \p c and \p next to \p os.
179 /// \return the number of characters written.
180 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
182 /// Output a space in appropriate formatting (or a surrogate pair
183 /// if the next character is a combining character).
184 /// \return whether a surrogate pair was output.
185 bool simpleTeXBlanks(Encoding const &,
186 odocstream &, TexRow & texrow,
188 unsigned int & column,
190 Layout const & style);
192 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
194 TexRow & texrow, OutputParams &,
197 Font const & outerfont,
199 Change & running_change,
200 Layout const & style,
202 unsigned int & column, value_type const c);
205 void validate(LaTeXFeatures & features,
206 Layout const & layout) const;
211 static unsigned int paragraph_id;
213 ParagraphParameters params;
217 pos_type size() const { return owner_->size(); }
218 /// match a string against a particular point in the paragraph
219 bool isTextAt(std::string const & str, pos_type pos) const;
221 /// for recording and looking up changes
232 using std::upper_bound;
233 using std::lower_bound;
237 // Initialization of the counter for the paragraph id's,
238 unsigned int Paragraph::Pimpl::paragraph_id = 0;
242 struct special_phrase {
248 special_phrase const special_phrases[] = {
249 { "LyX", from_ascii("\\LyX{}"), false },
250 { "TeX", from_ascii("\\TeX{}"), true },
251 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
252 { "LaTeX", from_ascii("\\LaTeX{}"), true },
255 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
260 Paragraph::Pimpl::Pimpl(Paragraph * owner)
264 id_ = paragraph_id++;
268 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
269 : params(p.params), changes_(p.changes_), owner_(owner)
271 inset_owner = p.inset_owner;
272 fontlist = p.fontlist;
273 id_ = paragraph_id++;
277 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
279 BOOST_ASSERT(start >= 0 && start <= size());
280 BOOST_ASSERT(end > start && end <= size() + 1);
282 return changes_.isChanged(start, end);
286 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
287 // keep the logic here in sync with the logic of eraseChars()
293 Change change = changes_.lookup(size());
295 return change.type == Change::INSERTED && change.author == 0;
299 void Paragraph::Pimpl::setChange(Change const & change)
301 // beware of the imaginary end-of-par character!
302 changes_.set(change, 0, size() + 1);
305 * Propagate the change recursively - but not in case of DELETED!
307 * Imagine that your co-author makes changes in an existing inset. He
308 * sends your document to you and you come to the conclusion that the
309 * inset should go completely. If you erase it, LyX must not delete all
310 * text within the inset. Otherwise, the change tracked insertions of
311 * your co-author get lost and there is no way to restore them later.
313 * Conclusion: An inset's content should remain untouched if you delete it
316 if (change.type != Change::DELETED) {
317 for (pos_type pos = 0; pos < size(); ++pos) {
318 if (owner_->isInset(pos)) {
319 owner_->getInset(pos)->setChange(change);
326 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
328 BOOST_ASSERT(pos >= 0 && pos <= size());
330 changes_.set(change, pos);
332 // see comment in setChange(Change const &) above
334 if (change.type != Change::DELETED &&
335 pos < size() && owner_->isInset(pos)) {
336 owner_->getInset(pos)->setChange(change);
341 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
343 BOOST_ASSERT(pos >= 0 && pos <= size());
345 return changes_.lookup(pos);
349 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
351 BOOST_ASSERT(start >= 0 && start <= size());
352 BOOST_ASSERT(end > start && end <= size() + 1);
354 for (pos_type pos = start; pos < end; ++pos) {
355 switch (lookupChange(pos).type) {
356 case Change::UNCHANGED:
357 // accept changes in nested inset
358 if (pos < size() && owner_->isInset(pos)) {
359 owner_->getInset(pos)->acceptChanges(bparams);
364 case Change::INSERTED:
365 changes_.set(Change(Change::UNCHANGED), pos);
366 // also accept changes in nested inset
367 if (pos < size() && owner_->isInset(pos)) {
368 owner_->getInset(pos)->acceptChanges(bparams);
372 case Change::DELETED:
373 // Suppress access to non-existent
374 // "end-of-paragraph char"
376 eraseChar(pos, false);
387 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
389 BOOST_ASSERT(start >= 0 && start <= size());
390 BOOST_ASSERT(end > start && end <= size() + 1);
392 for (pos_type pos = start; pos < end; ++pos) {
393 switch (lookupChange(pos).type) {
394 case Change::UNCHANGED:
395 // reject changes in nested inset
396 if (pos < size() && owner_->isInset(pos)) {
397 owner_->getInset(pos)->rejectChanges(bparams);
401 case Change::INSERTED:
402 // Suppress access to non-existent
403 // "end-of-paragraph char"
405 eraseChar(pos, false);
411 case Change::DELETED:
412 changes_.set(Change(Change::UNCHANGED), pos);
414 // Do NOT reject changes within a deleted inset!
415 // There may be insertions of a co-author inside of it!
423 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
425 BOOST_ASSERT(pos >= 0 && pos <= size());
427 return owner_->getChar(pos);
431 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
433 BOOST_ASSERT(pos >= 0 && pos <= size());
436 changes_.insert(change, pos);
438 // This is actually very common when parsing buffers (and
439 // maybe inserting ascii text)
441 // when appending characters, no need to update tables
442 owner_->text_.push_back(c);
446 owner_->text_.insert(owner_->text_.begin() + pos, c);
448 // Update the font table.
449 FontTable search_font(pos, Font());
450 for (FontList::iterator it
451 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
452 it != fontlist.end(); ++it)
454 it->pos(it->pos() + 1);
458 owner_->insetlist.increasePosAfterPos(pos);
462 void Paragraph::Pimpl::insertInset(pos_type pos, Inset * inset,
463 Change const & change)
466 BOOST_ASSERT(pos >= 0 && pos <= size());
468 insertChar(pos, META_INSET, change);
469 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
471 // Add a new entry in the insetlist.
472 owner_->insetlist.insert(inset, pos);
476 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
478 BOOST_ASSERT(pos >= 0 && pos <= size());
480 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
483 Change change = changes_.lookup(pos);
485 // set the character to DELETED if
486 // a) it was previously unchanged or
487 // b) it was inserted by a co-author
489 if (change.type == Change::UNCHANGED ||
490 (change.type == Change::INSERTED && change.author != 0)) {
491 setChange(pos, Change(Change::DELETED));
495 if (change.type == Change::DELETED)
499 // Don't physically access the imaginary end-of-paragraph character.
500 // eraseChar() can only mark it as DELETED. A physical deletion of
501 // end-of-par must be handled externally.
509 // if it is an inset, delete the inset entry
510 if (owner_->text_[pos] == Paragraph::META_INSET) {
511 owner_->insetlist.erase(pos);
514 owner_->text_.erase(owner_->text_.begin() + pos);
516 // Erase entries in the tables.
517 FontTable search_font(pos, Font());
519 FontList::iterator it =
520 lower_bound(fontlist.begin(),
522 search_font, matchFT());
523 if (it != fontlist.end() && it->pos() == pos &&
525 (it != fontlist.begin()
526 && boost::prior(it)->pos() == pos - 1))) {
527 // If it is a multi-character font
528 // entry, we just make it smaller
529 // (see update below), otherwise we
531 unsigned int const i = it - fontlist.begin();
532 fontlist.erase(fontlist.begin() + i);
533 it = fontlist.begin() + i;
534 if (i > 0 && i < fontlist.size() &&
535 fontlist[i - 1].font() == fontlist[i].font()) {
536 fontlist.erase(fontlist.begin() + i - 1);
537 it = fontlist.begin() + i - 1;
541 // Update all other entries
542 FontList::iterator fend = fontlist.end();
543 for (; it != fend; ++it)
544 it->pos(it->pos() - 1);
546 // Update the insetlist
547 owner_->insetlist.decreasePosAfterPos(pos);
553 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
555 BOOST_ASSERT(start >= 0 && start <= size());
556 BOOST_ASSERT(end >= start && end <= size() + 1);
559 for (pos_type count = end - start; count; --count) {
560 if (!eraseChar(i, trackChanges))
567 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
568 value_type next, Encoding const & encoding)
570 // Writing next here may circumvent a possible font change between
571 // c and next. Since next is only output if it forms a surrogate pair
572 // with c we can ignore this:
573 // A font change inside a surrogate pair does not make sense and is
574 // hopefully impossible to input.
575 // FIXME: change tracking
576 // Is this correct WRT change tracking?
577 docstring const latex1 = encoding.latexChar(next);
578 docstring const latex2 = encoding.latexChar(c);
579 os << latex1 << '{' << latex2 << '}';
580 return latex1.length() + latex2.length() + 2;
584 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
585 odocstream & os, TexRow & texrow,
587 unsigned int & column,
589 Layout const & style)
594 if (i < size() - 1) {
595 char_type next = getChar(i + 1);
596 if (Encodings::isCombiningChar(next)) {
597 // This space has an accent, so we must always output it.
598 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
604 if (lyxrc.plaintext_linelen > 0
605 && column > lyxrc.plaintext_linelen
607 && getChar(i - 1) != ' '
609 // same in FreeSpacing mode
610 && !owner_->isFreeSpacing()
611 // In typewriter mode, we want to avoid
612 // ! . ? : at the end of a line
613 && !(font.family() == Font::TYPEWRITER_FAMILY
614 && (getChar(i - 1) == '.'
615 || getChar(i - 1) == '?'
616 || getChar(i - 1) == ':'
617 || getChar(i - 1) == '!'))) {
620 texrow.start(owner_->id(), i + 1);
622 } else if (style.free_spacing) {
631 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
633 pos_type const len = str.length();
635 // is the paragraph large enough?
636 if (pos + len > size())
639 // does the wanted text start at point?
640 for (string::size_type i = 0; i < str.length(); ++i) {
641 // Caution: direct comparison of characters works only
642 // because str is pure ASCII.
643 if (str[i] != owner_->text_[pos + i])
647 // is there a font change in middle of the word?
648 FontList::const_iterator cit = fontlist.begin();
649 FontList::const_iterator end = fontlist.end();
650 for (; cit != end; ++cit) {
651 if (cit->pos() >= pos)
654 if (cit != end && pos + len - 1 > cit->pos())
661 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
662 BufferParams const & bparams,
665 OutputParams & runparams,
668 Font const & outerfont,
670 Change & running_change,
671 Layout const & style,
673 unsigned int & column,
676 if (style.pass_thru) {
677 if (c != Paragraph::META_INSET) {
679 // FIXME UNICODE: This can fail if c cannot
680 // be encoded in the current encoding.
683 owner_->getInset(i)->plaintext(buf, os, runparams);
687 // Two major modes: LaTeX or plain
688 // Handle here those cases common to both modes
689 // and then split to handle the two modes separately.
691 case Paragraph::META_INSET: {
692 Inset * inset = owner_->getInset(i);
694 // FIXME: remove this check
698 // FIXME: move this to InsetNewline::latex
699 if (inset->lyxCode() == Inset::NEWLINE_CODE) {
700 // newlines are handled differently here than
701 // the default in simpleTeXSpecialChars().
702 if (!style.newline_allowed) {
706 column += running_font.latexWriteEndChanges(
707 os, bparams, runparams,
712 if (running_font.family() == Font::TYPEWRITER_FAMILY)
715 basefont = owner_->getLayoutFont(bparams, outerfont);
716 running_font = basefont;
718 if (runparams.moving_arg)
724 texrow.start(owner_->id(), i + 1);
729 if (lookupChange(i).type == Change::DELETED) {
730 if( ++runparams.inDeletedInset == 1)
731 runparams.changeOfDeletedInset = lookupChange(i);
734 if (inset->canTrackChanges()) {
735 column += Changes::latexMarkChange(os, bparams, running_change,
736 Change(Change::UNCHANGED));
737 running_change = Change(Change::UNCHANGED);
741 odocstream::pos_type const len = os.tellp();
743 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
744 || inset->lyxCode() == Inset::MATH_CODE
745 || inset->lyxCode() == Inset::URL_CODE)
746 && running_font.isRightToLeft()) {
747 if (running_font.language()->lang() == "farsi")
754 // FIXME: Bug: we can have an empty font change here!
755 // if there has just been a font change, we are going to close it
756 // right now, which means stupid latex code like \textsf{}. AFAIK,
757 // this does not harm dvi output. A minor bug, thus (JMarc)
758 // Some insets cannot be inside a font change command.
759 // However, even such insets *can* be placed in \L or \R
760 // or their equivalents (for RTL language switches), so we don't
761 // close the language in those cases.
762 // ArabTeX, though, cannot handle this special behavior, it seems.
763 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
764 running_font.language()->lang() == "arabic_arabtex";
765 if (open_font && inset->noFontChange()) {
766 bool closeLanguage = arabtex ||
767 basefont.isRightToLeft() == running_font.isRightToLeft();
768 unsigned int count = running_font.latexWriteEndChanges(
769 os, bparams, runparams,
770 basefont, basefont, closeLanguage);
772 // if any font properties were closed, update the running_font,
773 // making sure, however, to leave the language as it was
775 // FIXME: probably a better way to keep track of the old
776 // language, than copying the entire font?
777 Font const copy_font(running_font);
778 basefont = owner_->getLayoutFont(bparams, outerfont);
779 running_font = basefont;
781 running_font.setLanguage(copy_font.language());
782 // leave font open if language is still open
783 open_font = (running_font.language() == basefont.language());
785 runparams.local_font = &basefont;
789 int tmp = inset->latex(buf, os, runparams);
792 if (running_font.language()->lang() == "farsi")
799 for (int j = 0; j < tmp; ++j) {
802 texrow.start(owner_->id(), i + 1);
805 column += os.tellp() - len;
808 if (lookupChange(i).type == Change::DELETED) {
809 --runparams.inDeletedInset;
815 // And now for the special cases within each mode
819 os << "\\textbackslash{}";
823 case '|': case '<': case '>':
824 // In T1 encoding, these characters exist
825 if (lyxrc.fontenc == "T1") {
827 //... but we should avoid ligatures
828 if ((c == '>' || c == '<')
830 && getChar(i + 1) == c) {
831 //os << "\\textcompwordmark{}";
833 // Jean-Marc, have a look at
834 // this. I think this works
842 // Typewriter font also has them
843 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
847 // Otherwise, we use what LaTeX
851 os << "\\textless{}";
855 os << "\\textgreater{}";
865 case '-': // "--" in Typewriter mode -> "-{}-"
866 if (i <= size() - 2 &&
867 getChar(i + 1) == '-' &&
868 running_font.family() == Font::TYPEWRITER_FAMILY) {
877 os << "\\char`\\\"{}";
882 case '%': case '#': case '{':
890 os << "\\textasciitilde{}";
895 os << "\\textasciicircum{}";
900 // avoid being mistaken for optional arguments
908 // Blanks are printed before font switching.
909 // Sure? I am not! (try nice-latex)
910 // I am sure it's correct. LyX might be smarter
911 // in the future, but for now, nothing wrong is
917 // I assume this is hack treating typewriter as verbatim
918 // FIXME UNICODE: This can fail if c cannot be encoded
919 // in the current encoding.
920 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
929 // FIXME: if we have "LaTeX" with a font
930 // change in the middle (before the 'T', then
931 // the "TeX" part is still special cased.
932 // Really we should only operate this on
933 // "words" for some definition of word
937 for (; pnr < phrases_nr; ++pnr) {
938 if (isTextAt(special_phrases[pnr].phrase, i)) {
939 os << special_phrases[pnr].macro;
940 i += special_phrases[pnr].phrase.length() - 1;
941 column += special_phrases[pnr].macro.length() - 1;
946 if (pnr == phrases_nr && c != '\0') {
947 Encoding const & encoding = *(runparams.encoding);
948 if (i < size() - 1) {
949 char_type next = getChar(i + 1);
950 if (Encodings::isCombiningChar(next)) {
951 column += latexSurrogatePair(os, c, next, encoding) - 1;
956 docstring const latex = encoding.latexChar(c);
957 if (latex.length() > 1 &&
958 latex[latex.length() - 1] != '}') {
959 // Prevent eating of a following
960 // space or command corruption by
961 // following characters
962 column += latex.length() + 1;
965 column += latex.length() - 1;
975 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
976 Layout const & layout) const
978 BufferParams const & bparams = features.bufferParams();
981 if (!params.spacing().isDefault())
982 features.require("setspace");
985 features.useLayout(layout.name());
988 Language const * doc_language = bparams.language;
990 FontList::const_iterator fcit = fontlist.begin();
991 FontList::const_iterator fend = fontlist.end();
992 for (; fcit != fend; ++fcit) {
993 if (fcit->font().noun() == Font::ON) {
994 LYXERR(Debug::LATEX) << "font.noun: "
995 << fcit->font().noun()
997 features.require("noun");
998 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
999 << to_utf8(fcit->font().stateText(0))
1002 switch (fcit->font().color()) {
1004 case Color::inherit:
1006 // probably we should put here all interface colors used for
1007 // font displaying! For now I just add this ones I know of (Jug)
1012 features.require("color");
1013 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1014 << to_utf8(fcit->font().stateText(0))
1018 Language const * language = fcit->font().language();
1019 if (language->babel() != doc_language->babel() &&
1020 language != ignore_language &&
1021 language != latex_language)
1023 features.useLanguage(language);
1024 LYXERR(Debug::LATEX) << "Found language "
1025 << language->lang() << endl;
1029 if (!params.leftIndent().zero())
1030 features.require("ParagraphLeftIndent");
1033 InsetList::const_iterator icit = owner_->insetlist.begin();
1034 InsetList::const_iterator iend = owner_->insetlist.end();
1035 for (; icit != iend; ++icit) {
1037 icit->inset->validate(features);
1038 if (layout.needprotect &&
1039 icit->inset->lyxCode() == Inset::FOOT_CODE)
1040 features.require("NeedLyXFootnoteCode");
1044 // then the contents
1045 for (pos_type i = 0; i < size() ; ++i) {
1046 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1047 if (!special_phrases[pnr].builtin
1048 && isTextAt(special_phrases[pnr].phrase, i)) {
1049 features.require(special_phrases[pnr].phrase);
1053 Encodings::validate(getChar(i), features);
1061 /////////////////////////////////////////////////////////////////////
1065 /////////////////////////////////////////////////////////////////////
1069 Paragraph::Paragraph()
1070 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1077 Paragraph::Paragraph(Paragraph const & par)
1078 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1079 layout_(par.layout_),
1080 text_(par.text_), begin_of_body_(par.begin_of_body_),
1081 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1083 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1084 InsetList::iterator it = insetlist.begin();
1085 InsetList::iterator end = insetlist.end();
1086 for (; it != end; ++it)
1087 it->inset = it->inset->clone().release();
1091 Paragraph & Paragraph::operator=(Paragraph const & par)
1093 // needed as we will destroy the pimpl_ before copying it
1095 itemdepth = par.itemdepth;
1097 insetlist = par.insetlist;
1098 InsetList::iterator it = insetlist.begin();
1099 InsetList::iterator end = insetlist.end();
1100 for (; it != end; ++it)
1101 it->inset = it->inset->clone().release();
1103 layout_ = par.layout();
1105 begin_of_body_ = par.begin_of_body_;
1108 pimpl_ = new Pimpl(*par.pimpl_, this);
1114 Paragraph::~Paragraph()
1118 //lyxerr << "Paragraph::paragraph_id = "
1119 // << Paragraph::paragraph_id << endl;
1123 void Paragraph::write(Buffer const & buf, ostream & os,
1124 BufferParams const & bparams,
1125 depth_type & dth) const
1127 // The beginning or end of a deeper (i.e. nested) area?
1128 if (dth != params().depth()) {
1129 if (params().depth() > dth) {
1130 while (params().depth() > dth) {
1131 os << "\n\\begin_deeper";
1135 while (params().depth() < dth) {
1136 os << "\n\\end_deeper";
1142 // First write the layout
1143 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1147 Font font1(Font::ALL_INHERIT, bparams.language);
1149 Change running_change = Change(Change::UNCHANGED);
1152 for (pos_type i = 0; i <= size(); ++i) {
1154 Change change = pimpl_->lookupChange(i);
1155 Changes::lyxMarkChange(os, column, running_change, change);
1156 running_change = change;
1161 // Write font changes
1162 Font font2 = getFontSettings(bparams, i);
1163 if (font2 != font1) {
1164 font2.lyxWriteChanges(font1, os);
1169 value_type const c = getChar(i);
1173 Inset const * inset = getInset(i);
1175 if (inset->directWrite()) {
1176 // international char, let it write
1177 // code directly so it's shorter in
1179 inset->write(buf, os);
1183 os << "\\begin_inset ";
1184 inset->write(buf, os);
1185 os << "\n\\end_inset\n\n";
1191 os << "\n\\backslash\n";
1195 if (i + 1 < size() && getChar(i + 1) == ' ') {
1202 if ((column > 70 && c == ' ')
1207 // this check is to amend a bug. LyX sometimes
1208 // inserts '\0' this could cause problems.
1210 std::vector<char> tmp = ucs4_to_utf8(c);
1211 tmp.push_back('\0');
1214 lyxerr << "ERROR (Paragraph::writeFile):"
1215 " NULL char in structure." << endl;
1221 os << "\n\\end_layout\n";
1225 void Paragraph::validate(LaTeXFeatures & features) const
1227 pimpl_->validate(features, *layout());
1231 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1233 return pimpl_->eraseChar(pos, trackChanges);
1237 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1239 return pimpl_->eraseChars(start, end, trackChanges);
1243 void Paragraph::insert(pos_type start, docstring const & str,
1244 Font const & font, Change const & change)
1246 for (size_t i = 0, n = str.size(); i != n ; ++i)
1247 insertChar(start + i, str[i], font, change);
1251 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1254 pimpl_->insertChar(pos, c, Change(trackChanges ?
1255 Change::INSERTED : Change::UNCHANGED));
1259 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1260 Font const & font, bool trackChanges)
1262 pimpl_->insertChar(pos, c, Change(trackChanges ?
1263 Change::INSERTED : Change::UNCHANGED));
1268 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1269 Font const & font, Change const & change)
1271 pimpl_->insertChar(pos, c, change);
1276 void Paragraph::insertInset(pos_type pos, Inset * inset,
1277 Change const & change)
1279 pimpl_->insertInset(pos, inset, change);
1283 void Paragraph::insertInset(pos_type pos, Inset * inset,
1284 Font const & font, Change const & change)
1286 pimpl_->insertInset(pos, inset, change);
1287 // Set the font/language of the inset...
1289 // ... as well as the font/language of the text inside the inset
1290 // FIXME: This is far from perfect. It basically overrides work being done
1291 // in the InsetText constructor. Also, it doesn't work for Tables
1292 // (precisely because each cell's font/language is set in the Table's
1293 // constructor, so by now it's too late). The long-term solution should
1294 // be moving current_font into Cursor, and getting rid of all this...
1295 // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1296 if (inset->asTextInset()) {
1297 inset->asTextInset()->text_.current_font = font;
1298 inset->asTextInset()->text_.real_current_font = font;
1303 bool Paragraph::insetAllowed(Inset_code code)
1305 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1309 // Gets uninstantiated font setting at position.
1310 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1314 lyxerr << " pos: " << pos << " size: " << size() << endl;
1315 BOOST_ASSERT(pos <= size());
1318 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1319 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1320 for (; cit != end; ++cit)
1321 if (cit->pos() >= pos)
1327 if (pos == size() && !empty())
1328 return getFontSettings(bparams, pos - 1);
1330 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1334 FontSpan Paragraph::fontSpan(pos_type pos) const
1336 BOOST_ASSERT(pos <= size());
1339 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1340 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1341 for (; cit != end; ++cit) {
1342 if (cit->pos() >= pos) {
1343 if (pos >= beginOfBody())
1344 return FontSpan(std::max(start, beginOfBody()),
1347 return FontSpan(start,
1348 std::min(beginOfBody() - 1,
1351 start = cit->pos() + 1;
1354 // This should not happen, but if so, we take no chances.
1355 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1357 return FontSpan(pos, pos);
1361 // Gets uninstantiated font setting at position 0
1362 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1364 if (!empty() && !pimpl_->fontlist.empty())
1365 return pimpl_->fontlist[0].font();
1367 return Font(Font::ALL_INHERIT, bparams.language);
1371 // Gets the fully instantiated font at a given position in a paragraph
1372 // This is basically the same function as Text::GetFont() in text2.cpp.
1373 // The difference is that this one is used for generating the LaTeX file,
1374 // and thus cosmetic "improvements" are disallowed: This has to deliver
1375 // the true picture of the buffer. (Asger)
1376 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1377 Font const & outerfont) const
1379 BOOST_ASSERT(pos >= 0);
1381 Font font = getFontSettings(bparams, pos);
1383 pos_type const body_pos = beginOfBody();
1385 font.realize(layout_->labelfont);
1387 font.realize(layout_->font);
1389 font.realize(outerfont);
1390 font.realize(bparams.getFont());
1396 Font const Paragraph::getLabelFont
1397 (BufferParams const & bparams, Font const & outerfont) const
1399 Font tmpfont = layout()->labelfont;
1400 tmpfont.setLanguage(getParLanguage(bparams));
1401 tmpfont.realize(outerfont);
1402 tmpfont.realize(bparams.getFont());
1407 Font const Paragraph::getLayoutFont
1408 (BufferParams const & bparams, Font const & outerfont) const
1410 Font tmpfont = layout()->font;
1411 tmpfont.setLanguage(getParLanguage(bparams));
1412 tmpfont.realize(outerfont);
1413 tmpfont.realize(bparams.getFont());
1418 /// Returns the height of the highest font in range
1419 Font_size Paragraph::highestFontInRange
1420 (pos_type startpos, pos_type endpos, Font_size def_size) const
1422 if (pimpl_->fontlist.empty())
1425 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1426 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1427 for (; end_it != end; ++end_it) {
1428 if (end_it->pos() >= endpos)
1435 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1436 for (; cit != end; ++cit) {
1437 if (cit->pos() >= startpos)
1441 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1442 for (; cit != end_it; ++cit) {
1443 Font::FONT_SIZE size = cit->font().size();
1444 if (size == Font::INHERIT_SIZE)
1446 if (size > maxsize && size <= Font::SIZE_HUGER)
1453 Paragraph::value_type
1454 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1456 value_type c = getChar(pos);
1457 if (!lyxrc.rtl_support)
1487 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1494 void Paragraph::setFont(pos_type pos, Font const & font)
1496 BOOST_ASSERT(pos <= size());
1498 // First, reduce font against layout/label font
1499 // Update: The setCharFont() routine in text2.cpp already
1500 // reduces font, so we don't need to do that here. (Asger)
1501 // No need to simplify this because it will disappear
1502 // in a new kernel. (Asger)
1503 // Next search font table
1505 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1506 Pimpl::FontList::iterator it = beg;
1507 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1508 for (; it != endit; ++it) {
1509 if (it->pos() >= pos)
1512 size_t const i = distance(beg, it);
1513 bool notfound = (it == endit);
1515 if (!notfound && pimpl_->fontlist[i].font() == font)
1518 bool begin = pos == 0 || notfound ||
1519 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1520 // Is position pos is a beginning of a font block?
1521 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1522 // Is position pos is the end of a font block?
1523 if (begin && end) { // A single char block
1524 if (i + 1 < pimpl_->fontlist.size() &&
1525 pimpl_->fontlist[i + 1].font() == font) {
1526 // Merge the singleton block with the next block
1527 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1528 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1529 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1530 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1531 // Merge the singleton block with the previous block
1532 pimpl_->fontlist[i - 1].pos(pos);
1533 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1535 pimpl_->fontlist[i].font(font);
1537 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1538 pimpl_->fontlist[i - 1].pos(pos);
1540 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1541 Pimpl::FontTable(pos, font));
1543 pimpl_->fontlist[i].pos(pos - 1);
1544 if (!(i + 1 < pimpl_->fontlist.size() &&
1545 pimpl_->fontlist[i + 1].font() == font))
1546 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1547 Pimpl::FontTable(pos, font));
1548 } else { // The general case. The block is splitted into 3 blocks
1549 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1550 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1551 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1552 Pimpl::FontTable(pos, font));
1557 void Paragraph::makeSameLayout(Paragraph const & par)
1559 layout(par.layout());
1561 params() = par.params();
1565 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1567 if (isFreeSpacing())
1573 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1574 if (eraseChar(pos, trackChanges))
1580 return count > 0 || pos > 0;
1584 bool Paragraph::hasSameLayout(Paragraph const & par) const
1586 return par.layout() == layout() && params().sameLayout(par.params());
1590 depth_type Paragraph::getDepth() const
1592 return params().depth();
1596 depth_type Paragraph::getMaxDepthAfter() const
1598 if (layout()->isEnvironment())
1599 return params().depth() + 1;
1601 return params().depth();
1605 char Paragraph::getAlign() const
1607 if (params().align() == LYX_ALIGN_LAYOUT)
1608 return layout()->align;
1610 return params().align();
1614 docstring const & Paragraph::getLabelstring() const
1616 return params().labelString();
1620 // the next two functions are for the manual labels
1621 docstring const Paragraph::getLabelWidthString() const
1623 if (!params().labelWidthString().empty())
1624 return params().labelWidthString();
1626 return _("Senseless with this layout!");
1630 void Paragraph::setLabelWidthString(docstring const & s)
1632 params().labelWidthString(s);
1636 docstring const Paragraph::translateIfPossible(docstring const & s,
1637 BufferParams const & bparams) const
1639 if (!support::isAscii(s) || s.empty()) {
1640 // This must be a user defined layout. We cannot translate
1641 // this, since gettext accepts only ascii keys.
1644 // Probably standard layout, try to translate
1645 Messages & m = getMessages(getParLanguage(bparams)->code());
1646 return m.get(to_ascii(s));
1650 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1651 BufferParams const & bparams, bool process_appendix) const
1653 TextClass const & tclass = bparams.getTextClass();
1656 if (process_appendix && params().appendix())
1657 fmt = translateIfPossible(layout->labelstring_appendix(),
1660 fmt = translateIfPossible(layout->labelstring(), bparams);
1662 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1663 && !layout->counter.empty())
1664 fmt = "\\the" + layout->counter;
1666 // handle 'inherited level parts' in 'fmt',
1667 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1668 size_t const i = fmt.find('@', 0);
1669 if (i != docstring::npos) {
1670 size_t const j = fmt.find('@', i + 1);
1671 if (j != docstring::npos) {
1672 docstring parent(fmt, i + 1, j - i - 1);
1673 docstring label = expandLabel(tclass[parent], bparams,
1675 fmt = docstring(fmt, 0, i) + label
1676 + docstring(fmt, j + 1, docstring::npos);
1680 return tclass.counters().counterLabel(fmt);
1684 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1687 LyXAlignment const oldAlign = params().align();
1689 if (!(oldAlign & layout()->alignpossible)) {
1690 frontend::Alert::warning(_("Alignment not permitted"),
1691 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1692 params().align(LYX_ALIGN_LAYOUT);
1697 pos_type Paragraph::beginOfBody() const
1699 return begin_of_body_;
1703 void Paragraph::setBeginOfBody()
1705 if (layout()->labeltype != LABEL_MANUAL) {
1710 // Unroll the first two cycles of the loop
1711 // and remember the previous character to
1712 // remove unnecessary getChar() calls
1714 pos_type end = size();
1715 if (i < end && !isNewline(i)) {
1717 char_type previous_char = 0;
1720 previous_char = text_[i];
1721 if (!isNewline(i)) {
1723 while (i < end && previous_char != ' ') {
1728 previous_char = temp;
1738 // returns -1 if inset not found
1739 int Paragraph::getPositionOfInset(Inset const * inset) const
1742 InsetList::const_iterator it = insetlist.begin();
1743 InsetList::const_iterator end = insetlist.end();
1744 for (; it != end; ++it)
1745 if (it->inset == inset)
1751 InsetBibitem * Paragraph::bibitem() const
1753 if (!insetlist.empty()) {
1754 Inset * inset = insetlist.begin()->inset;
1755 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1756 return static_cast<InsetBibitem *>(inset);
1762 bool Paragraph::forceDefaultParagraphs() const
1764 return inInset() && inInset()->forceDefaultParagraphs(0);
1770 // paragraphs inside floats need different alignment tags to avoid
1773 bool noTrivlistCentering(Inset::Code code)
1775 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1779 string correction(string const & orig)
1781 if (orig == "flushleft")
1782 return "raggedright";
1783 if (orig == "flushright")
1784 return "raggedleft";
1785 if (orig == "center")
1791 string const corrected_env(string const & suffix, string const & env,
1794 string output = suffix + "{";
1795 if (noTrivlistCentering(code))
1796 output += correction(env);
1800 if (suffix == "\\begin")
1806 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1808 if (!contains(str, "\n"))
1809 column += str.size();
1813 column = rsplit(str, tmp, '\n').size();
1820 // This could go to ParagraphParameters if we want to
1821 int Paragraph::startTeXParParams(BufferParams const & bparams,
1822 odocstream & os, TexRow & texrow,
1823 bool moving_arg) const
1827 if (params().noindent()) {
1828 os << "\\noindent ";
1832 LyXAlignment const curAlign = params().align();
1834 if (curAlign == layout()->align)
1838 case LYX_ALIGN_NONE:
1839 case LYX_ALIGN_BLOCK:
1840 case LYX_ALIGN_LAYOUT:
1841 case LYX_ALIGN_SPECIAL:
1843 case LYX_ALIGN_LEFT:
1844 case LYX_ALIGN_RIGHT:
1845 case LYX_ALIGN_CENTER:
1854 case LYX_ALIGN_NONE:
1855 case LYX_ALIGN_BLOCK:
1856 case LYX_ALIGN_LAYOUT:
1857 case LYX_ALIGN_SPECIAL:
1859 case LYX_ALIGN_LEFT: {
1861 if (getParLanguage(bparams)->babel() != "hebrew")
1862 output = corrected_env("\\begin", "flushleft", ownerCode());
1864 output = corrected_env("\\begin", "flushright", ownerCode());
1865 os << from_ascii(output);
1866 adjust_row_column(output, texrow, column);
1868 } case LYX_ALIGN_RIGHT: {
1870 if (getParLanguage(bparams)->babel() != "hebrew")
1871 output = corrected_env("\\begin", "flushright", ownerCode());
1873 output = corrected_env("\\begin", "flushleft", ownerCode());
1874 os << from_ascii(output);
1875 adjust_row_column(output, texrow, column);
1877 } case LYX_ALIGN_CENTER: {
1879 output = corrected_env("\\begin", "center", ownerCode());
1880 os << from_ascii(output);
1881 adjust_row_column(output, texrow, column);
1890 // This could go to ParagraphParameters if we want to
1891 int Paragraph::endTeXParParams(BufferParams const & bparams,
1892 odocstream & os, TexRow & texrow,
1893 bool moving_arg) const
1897 switch (params().align()) {
1898 case LYX_ALIGN_NONE:
1899 case LYX_ALIGN_BLOCK:
1900 case LYX_ALIGN_LAYOUT:
1901 case LYX_ALIGN_SPECIAL:
1903 case LYX_ALIGN_LEFT:
1904 case LYX_ALIGN_RIGHT:
1905 case LYX_ALIGN_CENTER:
1913 switch (params().align()) {
1914 case LYX_ALIGN_NONE:
1915 case LYX_ALIGN_BLOCK:
1916 case LYX_ALIGN_LAYOUT:
1917 case LYX_ALIGN_SPECIAL:
1919 case LYX_ALIGN_LEFT: {
1921 if (getParLanguage(bparams)->babel() != "hebrew")
1922 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1924 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1925 os << from_ascii(output);
1926 adjust_row_column(output, texrow, column);
1928 } case LYX_ALIGN_RIGHT: {
1930 if (getParLanguage(bparams)->babel() != "hebrew")
1931 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1933 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1934 os << from_ascii(output);
1935 adjust_row_column(output, texrow, column);
1937 } case LYX_ALIGN_CENTER: {
1939 output = corrected_env("\n\\par\\end", "center", ownerCode());
1940 os << from_ascii(output);
1941 adjust_row_column(output, texrow, column);
1950 // This one spits out the text of the paragraph
1951 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1952 BufferParams const & bparams,
1953 Font const & outerfont,
1954 odocstream & os, TexRow & texrow,
1955 OutputParams const & runparams) const
1957 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1959 bool return_value = false;
1963 // well we have to check if we are in an inset with unlimited
1964 // length (all in one row) if that is true then we don't allow
1965 // any special options in the paragraph and also we don't allow
1966 // any environment other than the default layout of the text class
1968 bool asdefault = forceDefaultParagraphs();
1971 style = bparams.getTextClass().defaultLayout();
1976 // Current base font for all inherited font changes, without any
1977 // change caused by an individual character, except for the language:
1978 // It is set to the language of the first character.
1979 // As long as we are in the label, this font is the base font of the
1980 // label. Before the first body character it is set to the base font
1984 // Maybe we have to create a optional argument.
1985 pos_type body_pos = beginOfBody();
1986 unsigned int column = 0;
1989 // the optional argument is kept in curly brackets in
1990 // case it contains a ']'
1993 basefont = getLabelFont(bparams, outerfont);
1995 basefont = getLayoutFont(bparams, outerfont);
1998 // Which font is currently active?
1999 Font running_font(basefont);
2000 // Do we have an open font change?
2001 bool open_font = false;
2003 Change runningChange = Change(Change::UNCHANGED);
2005 texrow.start(id(), 0);
2007 // if the paragraph is empty, the loop will not be entered at all
2009 if (style->isCommand()) {
2014 column += startTeXParParams(bparams, os, texrow,
2015 runparams.moving_arg);
2018 for (pos_type i = 0; i < size(); ++i) {
2019 // First char in paragraph or after label?
2020 if (i == body_pos) {
2023 column += running_font.latexWriteEndChanges(
2024 os, bparams, runparams,
2025 basefont, basefont);
2028 basefont = getLayoutFont(bparams, outerfont);
2029 running_font = basefont;
2031 column += Changes::latexMarkChange(os, bparams,
2032 runningChange, Change(Change::UNCHANGED));
2033 runningChange = Change(Change::UNCHANGED);
2038 if (style->isCommand()) {
2044 column += startTeXParParams(bparams, os,
2046 runparams.moving_arg);
2049 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2050 : pimpl_->lookupChange(i);
2052 if (bparams.outputChanges && runningChange != change) {
2054 column += running_font.latexWriteEndChanges(
2055 os, bparams, runparams, basefont, basefont);
2058 basefont = getLayoutFont(bparams, outerfont);
2059 running_font = basefont;
2061 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2062 runningChange = change;
2065 // do not output text which is marked deleted
2066 // if change tracking output is disabled
2067 if (!bparams.outputChanges && change.type == Change::DELETED) {
2073 value_type const c = getChar(i);
2075 // Fully instantiated font
2076 Font const font = getFont(bparams, i, outerfont);
2078 Font const last_font = running_font;
2080 // Do we need to close the previous font?
2082 (font != running_font ||
2083 font.language() != running_font.language()))
2085 column += running_font.latexWriteEndChanges(
2086 os, bparams, runparams, basefont,
2087 (i == body_pos-1) ? basefont : font);
2088 running_font = basefont;
2092 // Switch file encoding if necessary
2093 if (runparams.encoding->package() == Encoding::inputenc &&
2094 font.language()->encoding()->package() == Encoding::inputenc) {
2095 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2096 runparams.moving_arg, *(runparams.encoding),
2097 *(font.language()->encoding()));
2098 if (enc_switch.first) {
2099 column += enc_switch.second;
2100 runparams.encoding = font.language()->encoding();
2104 // Do we need to change font?
2105 if ((font != running_font ||
2106 font.language() != running_font.language()) &&
2109 odocstringstream ods;
2110 column += font.latexWriteStartChanges(ods, bparams,
2111 runparams, basefont,
2113 running_font = font;
2115 docstring fontchange = ods.str();
2116 // check if the fontchange ends with a trailing blank
2117 // (like "\small " (see bug 3382)
2118 if (suffixIs(fontchange, ' ') && c == ' ')
2119 os << fontchange.substr(0, fontchange.size() - 1)
2120 << from_ascii("{}");
2126 // Do not print the separation of the optional argument
2127 // if style->pass_thru is false. This works because
2128 // simpleTeXSpecialChars ignores spaces if
2129 // style->pass_thru is false.
2130 if (i != body_pos - 1) {
2131 if (pimpl_->simpleTeXBlanks(
2132 *(runparams.encoding), os, texrow,
2133 i, column, font, *style))
2134 // A surrogate pair was output. We
2135 // must not call simpleTeXSpecialChars
2136 // in this iteration, since
2137 // simpleTeXBlanks incremented i, and
2138 // simpleTeXSpecialChars would output
2139 // the combining character again.
2144 OutputParams rp = runparams;
2145 rp.free_spacing = style->free_spacing;
2146 rp.local_font = &font;
2147 rp.intitle = style->intitle;
2148 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2149 texrow, rp, running_font,
2150 basefont, outerfont, open_font,
2151 runningChange, *style, i, column, c);
2153 // Set the encoding to that returned from simpleTeXSpecialChars (see
2154 // comment for encoding member in OutputParams.h)
2155 runparams.encoding = rp.encoding;
2158 // If we have an open font definition, we have to close it
2160 #ifdef FIXED_LANGUAGE_END_DETECTION
2163 .latexWriteEndChanges(os, bparams, runparams,
2165 next_->getFont(bparams, 0, outerfont));
2167 running_font.latexWriteEndChanges(os, bparams,
2168 runparams, basefont, basefont);
2171 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2172 //FIXME: there as we start another \selectlanguage with the next paragraph if
2173 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2174 running_font.latexWriteEndChanges(os, bparams, runparams,
2175 basefont, basefont);
2179 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2181 // Needed if there is an optional argument but no contents.
2182 if (body_pos > 0 && body_pos == size()) {
2184 return_value = false;
2188 column += endTeXParParams(bparams, os, texrow,
2189 runparams.moving_arg);
2192 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2193 return return_value;
2210 string tag_name(PAR_TAG const & pt) {
2212 case PAR_NONE: return "!-- --";
2213 case TT: return "tt";
2214 case SF: return "sf";
2215 case BF: return "bf";
2216 case IT: return "it";
2217 case SL: return "sl";
2218 case EM: return "em";
2225 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2227 p1 = static_cast<PAR_TAG>(p1 | p2);
2232 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2234 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2240 bool Paragraph::emptyTag() const
2242 for (pos_type i = 0; i < size(); ++i) {
2244 Inset const * inset = getInset(i);
2245 Inset::Code lyx_code = inset->lyxCode();
2246 if (lyx_code != Inset::TOC_CODE &&
2247 lyx_code != Inset::INCLUDE_CODE &&
2248 lyx_code != Inset::GRAPHICS_CODE &&
2249 lyx_code != Inset::ERT_CODE &&
2250 lyx_code != Inset::LISTINGS_CODE &&
2251 lyx_code != Inset::FLOAT_CODE &&
2252 lyx_code != Inset::TABULAR_CODE) {
2256 value_type c = getChar(i);
2257 if (c != ' ' && c != '\t')
2265 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2267 for (pos_type i = 0; i < size(); ++i) {
2269 Inset const * inset = getInset(i);
2270 Inset::Code lyx_code = inset->lyxCode();
2271 if (lyx_code == Inset::LABEL_CODE) {
2272 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2273 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2282 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2285 for (i = 0; i < size(); ++i) {
2287 Inset const * inset = getInset(i);
2288 inset->docbook(buf, os, runparams);
2290 value_type c = getChar(i);
2293 os << sgml::escapeChar(c);
2300 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2304 for (pos_type i = initial; i < size(); ++i) {
2305 Font font = getFont(buf.params(), i, outerfont);
2308 if (i != initial && font != font_old)
2317 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2319 OutputParams const & runparams,
2320 Font const & outerfont,
2321 pos_type initial) const
2323 bool emph_flag = false;
2325 LayoutPtr const & style = layout();
2327 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2329 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2332 // parsing main loop
2333 for (pos_type i = initial; i < size(); ++i) {
2334 Font font = getFont(buf.params(), i, outerfont);
2336 // handle <emphasis> tag
2337 if (font_old.emph() != font.emph()) {
2338 if (font.emph() == Font::ON) {
2341 } else if (i != initial) {
2342 os << "</emphasis>";
2348 Inset const * inset = getInset(i);
2349 inset->docbook(buf, os, runparams);
2351 value_type c = getChar(i);
2353 if (style->pass_thru)
2356 os << sgml::escapeChar(c);
2362 os << "</emphasis>";
2365 if (style->free_spacing)
2367 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2372 bool Paragraph::isNewline(pos_type pos) const
2375 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2379 bool Paragraph::isLineSeparator(pos_type pos) const
2381 value_type const c = getChar(pos);
2382 return isLineSeparatorChar(c)
2383 || (c == Paragraph::META_INSET && getInset(pos) &&
2384 getInset(pos)->isLineSeparator());
2388 /// Used by the spellchecker
2389 bool Paragraph::isLetter(pos_type pos) const
2392 return getInset(pos)->isLetter();
2394 value_type const c = getChar(pos);
2395 return isLetterChar(c) || isDigit(c);
2401 Paragraph::getParLanguage(BufferParams const & bparams) const
2404 return getFirstFontSettings(bparams).language();
2405 // FIXME: we should check the prev par as well (Lgb)
2406 return bparams.language;
2410 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2412 return lyxrc.rtl_support
2413 && getParLanguage(bparams)->rightToLeft()
2414 && ownerCode() != Inset::ERT_CODE
2415 && ownerCode() != Inset::LISTINGS_CODE;
2419 void Paragraph::changeLanguage(BufferParams const & bparams,
2420 Language const * from, Language const * to)
2422 // change language including dummy font change at the end
2423 for (pos_type i = 0; i <= size(); ++i) {
2424 Font font = getFontSettings(bparams, i);
2425 if (font.language() == from) {
2426 font.setLanguage(to);
2433 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2435 Language const * doc_language = bparams.language;
2436 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2437 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2439 for (; cit != end; ++cit)
2440 if (cit->font().language() != ignore_language &&
2441 cit->font().language() != latex_language &&
2442 cit->font().language() != doc_language)
2448 // Convert the paragraph to a string.
2449 // Used for building the table of contents
2450 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2452 return asString(buffer, 0, size(), label);
2456 docstring const Paragraph::asString(Buffer const & buffer,
2457 pos_type beg, pos_type end, bool label) const
2460 odocstringstream os;
2462 if (beg == 0 && label && !params().labelString().empty())
2463 os << params().labelString() << ' ';
2465 for (pos_type i = beg; i < end; ++i) {
2466 value_type const c = getChar(i);
2469 else if (c == META_INSET)
2470 getInset(i)->textString(buffer, os);
2477 void Paragraph::setInsetOwner(Inset * inset)
2479 pimpl_->inset_owner = inset;
2483 Change const & Paragraph::lookupChange(pos_type pos) const
2485 BOOST_ASSERT(pos <= size());
2486 return pimpl_->lookupChange(pos);
2490 bool Paragraph::isChanged(pos_type start, pos_type end) const
2492 return pimpl_->isChanged(start, end);
2496 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2498 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2502 void Paragraph::setChange(Change const & change)
2504 pimpl_->setChange(change);
2508 void Paragraph::setChange(pos_type pos, Change const & change)
2510 pimpl_->setChange(pos, change);
2514 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2516 return pimpl_->acceptChanges(bparams, start, end);
2520 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2522 return pimpl_->rejectChanges(bparams, start, end);
2526 int Paragraph::id() const
2532 LayoutPtr const & Paragraph::layout() const
2538 void Paragraph::layout(LayoutPtr const & new_layout)
2540 layout_ = new_layout;
2544 Inset * Paragraph::inInset() const
2546 return pimpl_->inset_owner;
2550 Inset::Code Paragraph::ownerCode() const
2552 return pimpl_->inset_owner
2553 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2557 ParagraphParameters & Paragraph::params()
2559 return pimpl_->params;
2563 ParagraphParameters const & Paragraph::params() const
2565 return pimpl_->params;
2569 bool Paragraph::isFreeSpacing() const
2571 if (layout()->free_spacing)
2574 // for now we just need this, later should we need this in some
2575 // other way we can always add a function to Inset too.
2576 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2580 bool Paragraph::allowEmpty() const
2582 if (layout()->keepempty)
2584 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2588 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2590 if (!Encodings::is_arabic(c))
2593 value_type prev_char = ' ';
2594 value_type next_char = ' ';
2596 for (pos_type i = pos - 1; i >= 0; --i) {
2597 value_type const par_char = getChar(i);
2598 if (!Encodings::isComposeChar_arabic(par_char)) {
2599 prev_char = par_char;
2604 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2605 value_type const par_char = getChar(i);
2606 if (!Encodings::isComposeChar_arabic(par_char)) {
2607 next_char = par_char;
2612 if (Encodings::is_arabic(next_char)) {
2613 if (Encodings::is_arabic(prev_char) &&
2614 !Encodings::is_arabic_special(prev_char))
2615 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2617 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2619 if (Encodings::is_arabic(prev_char) &&
2620 !Encodings::is_arabic_special(prev_char))
2621 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2623 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2628 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2633 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2635 // expand at the end of a row only if there is another hfill on the same row
2636 if (pos == row.endpos() - 1) {
2637 for (pos_type i = row.pos(); i < pos; i++) {
2644 // expand at the beginning of a row only if it is the first row of a paragraph
2645 if (pos == row.pos()) {
2649 // do not expand in some labels
2650 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2653 // if there is anything between the first char of the row and
2654 // the specified position that is neither a newline nor an hfill,
2655 // the hfill will be expanded, otherwise it won't
2656 for (pos_type i = row.pos(); i < pos; i++) {
2657 if (!isNewline(i) && !isHfill(i))
2664 int Paragraph::checkBiblio(bool track_changes)
2667 //This is getting more and more a mess. ...We really should clean
2668 //up this bibitem issue for 1.6. See also bug 2743.
2670 // Add bibitem insets if necessary
2671 if (layout()->labeltype != LABEL_BIBLIO)
2674 bool hasbibitem = !insetlist.empty()
2675 // Insist on it being in pos 0
2676 && getChar(0) == Paragraph::META_INSET
2677 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2682 // remove a bibitem in pos != 0
2683 // restore it later in pos 0 if necessary
2684 // (e.g. if a user inserts contents _before_ the item)
2685 // we're assuming there's only one of these, which there
2687 int erasedInsetPosition = -1;
2688 InsetList::iterator it = insetlist.begin();
2689 InsetList::iterator end = insetlist.end();
2690 for (; it != end; ++it)
2691 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2693 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2694 oldkey = olditem->getParam("key");
2695 oldlabel = olditem->getParam("label");
2696 erasedInsetPosition = it->pos;
2697 eraseChar(erasedInsetPosition, track_changes);
2701 //There was an InsetBibitem at the beginning, and we didn't
2702 //have to erase one.
2703 if (hasbibitem && erasedInsetPosition < 0)
2706 //There was an InsetBibitem at the beginning and we did have to
2707 //erase one. So we give its properties to the beginning inset.
2709 InsetBibitem * inset =
2710 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2711 if (!oldkey.empty())
2712 inset->setParam("key", oldkey);
2713 inset->setParam("label", oldlabel);
2714 return -erasedInsetPosition;
2717 //There was no inset at the beginning, so we need to create one with
2718 //the key and label of the one we erased.
2719 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2720 // restore values of previously deleted item in this par.
2721 if (!oldkey.empty())
2722 inset->setParam("key", oldkey);
2723 inset->setParam("label", oldlabel);
2724 insertInset(0, static_cast<Inset *>(inset),
2725 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2731 void Paragraph::checkAuthors(AuthorList const & authorList)
2733 pimpl_->changes_.checkAuthors(authorList);