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 Layout_ptr const & lout = layout();
1383 pos_type const body_pos = beginOfBody();
1387 layoutfont = lout->labelfont;
1389 layoutfont = lout->font;
1391 Font font = getFontSettings(bparams, pos);
1392 font.realize(layoutfont);
1393 font.realize(outerfont);
1394 font.realize(bparams.getFont());
1400 Font const Paragraph::getLabelFont
1401 (BufferParams const & bparams, Font const & outerfont) const
1403 Font tmpfont = layout()->labelfont;
1404 tmpfont.setLanguage(getParLanguage(bparams));
1405 tmpfont.realize(outerfont);
1406 tmpfont.realize(bparams.getFont());
1411 Font const Paragraph::getLayoutFont
1412 (BufferParams const & bparams, Font const & outerfont) const
1414 Font tmpfont = layout()->font;
1415 tmpfont.setLanguage(getParLanguage(bparams));
1416 tmpfont.realize(outerfont);
1417 tmpfont.realize(bparams.getFont());
1422 /// Returns the height of the highest font in range
1423 Font_size Paragraph::highestFontInRange
1424 (pos_type startpos, pos_type endpos, Font_size def_size) const
1426 if (pimpl_->fontlist.empty())
1429 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1430 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1431 for (; end_it != end; ++end_it) {
1432 if (end_it->pos() >= endpos)
1439 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1440 for (; cit != end; ++cit) {
1441 if (cit->pos() >= startpos)
1445 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1446 for (; cit != end_it; ++cit) {
1447 Font::FONT_SIZE size = cit->font().size();
1448 if (size == Font::INHERIT_SIZE)
1450 if (size > maxsize && size <= Font::SIZE_HUGER)
1457 Paragraph::value_type
1458 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1460 value_type c = getChar(pos);
1461 if (!lyxrc.rtl_support)
1491 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1498 void Paragraph::setFont(pos_type pos, Font const & font)
1500 BOOST_ASSERT(pos <= size());
1502 // First, reduce font against layout/label font
1503 // Update: The setCharFont() routine in text2.cpp already
1504 // reduces font, so we don't need to do that here. (Asger)
1505 // No need to simplify this because it will disappear
1506 // in a new kernel. (Asger)
1507 // Next search font table
1509 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1510 Pimpl::FontList::iterator it = beg;
1511 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1512 for (; it != endit; ++it) {
1513 if (it->pos() >= pos)
1516 size_t const i = distance(beg, it);
1517 bool notfound = (it == endit);
1519 if (!notfound && pimpl_->fontlist[i].font() == font)
1522 bool begin = pos == 0 || notfound ||
1523 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1524 // Is position pos is a beginning of a font block?
1525 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1526 // Is position pos is the end of a font block?
1527 if (begin && end) { // A single char block
1528 if (i + 1 < pimpl_->fontlist.size() &&
1529 pimpl_->fontlist[i + 1].font() == font) {
1530 // Merge the singleton block with the next block
1531 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1532 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1533 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1534 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1535 // Merge the singleton block with the previous block
1536 pimpl_->fontlist[i - 1].pos(pos);
1537 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1539 pimpl_->fontlist[i].font(font);
1541 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1542 pimpl_->fontlist[i - 1].pos(pos);
1544 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1545 Pimpl::FontTable(pos, font));
1547 pimpl_->fontlist[i].pos(pos - 1);
1548 if (!(i + 1 < pimpl_->fontlist.size() &&
1549 pimpl_->fontlist[i + 1].font() == font))
1550 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1551 Pimpl::FontTable(pos, font));
1552 } else { // The general case. The block is splitted into 3 blocks
1553 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1554 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1555 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1556 Pimpl::FontTable(pos, font));
1561 void Paragraph::makeSameLayout(Paragraph const & par)
1563 layout(par.layout());
1565 params() = par.params();
1569 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1571 if (isFreeSpacing())
1577 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1578 if (eraseChar(pos, trackChanges))
1584 return count > 0 || pos > 0;
1588 bool Paragraph::hasSameLayout(Paragraph const & par) const
1590 return par.layout() == layout() && params().sameLayout(par.params());
1594 depth_type Paragraph::getDepth() const
1596 return params().depth();
1600 depth_type Paragraph::getMaxDepthAfter() const
1602 if (layout()->isEnvironment())
1603 return params().depth() + 1;
1605 return params().depth();
1609 char Paragraph::getAlign() const
1611 if (params().align() == LYX_ALIGN_LAYOUT)
1612 return layout()->align;
1614 return params().align();
1618 docstring const & Paragraph::getLabelstring() const
1620 return params().labelString();
1624 // the next two functions are for the manual labels
1625 docstring const Paragraph::getLabelWidthString() const
1627 if (!params().labelWidthString().empty())
1628 return params().labelWidthString();
1630 return _("Senseless with this layout!");
1634 void Paragraph::setLabelWidthString(docstring const & s)
1636 params().labelWidthString(s);
1640 docstring const Paragraph::translateIfPossible(docstring const & s,
1641 BufferParams const & bparams) const
1643 if (!support::isAscii(s) || s.empty()) {
1644 // This must be a user defined layout. We cannot translate
1645 // this, since gettext accepts only ascii keys.
1648 // Probably standard layout, try to translate
1649 Messages & m = getMessages(getParLanguage(bparams)->code());
1650 return m.get(to_ascii(s));
1654 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1655 BufferParams const & bparams, bool process_appendix) const
1657 TextClass const & tclass = bparams.getTextClass();
1660 if (process_appendix && params().appendix())
1661 fmt = translateIfPossible(layout->labelstring_appendix(),
1664 fmt = translateIfPossible(layout->labelstring(), bparams);
1666 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1667 && !layout->counter.empty())
1668 fmt = "\\the" + layout->counter;
1670 // handle 'inherited level parts' in 'fmt',
1671 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1672 size_t const i = fmt.find('@', 0);
1673 if (i != docstring::npos) {
1674 size_t const j = fmt.find('@', i + 1);
1675 if (j != docstring::npos) {
1676 docstring parent(fmt, i + 1, j - i - 1);
1677 docstring label = expandLabel(tclass[parent], bparams,
1679 fmt = docstring(fmt, 0, i) + label
1680 + docstring(fmt, j + 1, docstring::npos);
1684 return tclass.counters().counterLabel(fmt);
1688 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1691 LyXAlignment const oldAlign = params().align();
1693 if (!(oldAlign & layout()->alignpossible)) {
1694 frontend::Alert::warning(_("Alignment not permitted"),
1695 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1696 params().align(LYX_ALIGN_LAYOUT);
1701 pos_type Paragraph::beginOfBody() const
1703 return begin_of_body_;
1707 void Paragraph::setBeginOfBody()
1709 if (layout()->labeltype != LABEL_MANUAL) {
1714 // Unroll the first two cycles of the loop
1715 // and remember the previous character to
1716 // remove unnecessary getChar() calls
1718 pos_type end = size();
1719 if (i < end && !isNewline(i)) {
1721 char_type previous_char = 0;
1724 previous_char = text_[i];
1725 if (!isNewline(i)) {
1727 while (i < end && previous_char != ' ') {
1732 previous_char = temp;
1742 // returns -1 if inset not found
1743 int Paragraph::getPositionOfInset(Inset const * inset) const
1746 InsetList::const_iterator it = insetlist.begin();
1747 InsetList::const_iterator end = insetlist.end();
1748 for (; it != end; ++it)
1749 if (it->inset == inset)
1755 InsetBibitem * Paragraph::bibitem() const
1757 if (!insetlist.empty()) {
1758 Inset * inset = insetlist.begin()->inset;
1759 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1760 return static_cast<InsetBibitem *>(inset);
1766 bool Paragraph::forceDefaultParagraphs() const
1768 return inInset() && inInset()->forceDefaultParagraphs(0);
1774 // paragraphs inside floats need different alignment tags to avoid
1777 bool noTrivlistCentering(Inset::Code code)
1779 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1783 string correction(string const & orig)
1785 if (orig == "flushleft")
1786 return "raggedright";
1787 if (orig == "flushright")
1788 return "raggedleft";
1789 if (orig == "center")
1795 string const corrected_env(string const & suffix, string const & env,
1798 string output = suffix + "{";
1799 if (noTrivlistCentering(code))
1800 output += correction(env);
1804 if (suffix == "\\begin")
1810 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1812 if (!contains(str, "\n"))
1813 column += str.size();
1817 column = rsplit(str, tmp, '\n').size();
1824 // This could go to ParagraphParameters if we want to
1825 int Paragraph::startTeXParParams(BufferParams const & bparams,
1826 odocstream & os, TexRow & texrow,
1827 bool moving_arg) const
1831 if (params().noindent()) {
1832 os << "\\noindent ";
1836 LyXAlignment const curAlign = params().align();
1838 if (curAlign == layout()->align)
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:
1848 case LYX_ALIGN_RIGHT:
1849 case LYX_ALIGN_CENTER:
1858 case LYX_ALIGN_NONE:
1859 case LYX_ALIGN_BLOCK:
1860 case LYX_ALIGN_LAYOUT:
1861 case LYX_ALIGN_SPECIAL:
1863 case LYX_ALIGN_LEFT: {
1865 if (getParLanguage(bparams)->babel() != "hebrew")
1866 output = corrected_env("\\begin", "flushleft", ownerCode());
1868 output = corrected_env("\\begin", "flushright", ownerCode());
1869 os << from_ascii(output);
1870 adjust_row_column(output, texrow, column);
1872 } case LYX_ALIGN_RIGHT: {
1874 if (getParLanguage(bparams)->babel() != "hebrew")
1875 output = corrected_env("\\begin", "flushright", ownerCode());
1877 output = corrected_env("\\begin", "flushleft", ownerCode());
1878 os << from_ascii(output);
1879 adjust_row_column(output, texrow, column);
1881 } case LYX_ALIGN_CENTER: {
1883 output = corrected_env("\\begin", "center", ownerCode());
1884 os << from_ascii(output);
1885 adjust_row_column(output, texrow, column);
1894 // This could go to ParagraphParameters if we want to
1895 int Paragraph::endTeXParParams(BufferParams const & bparams,
1896 odocstream & os, TexRow & texrow,
1897 bool moving_arg) const
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:
1908 case LYX_ALIGN_RIGHT:
1909 case LYX_ALIGN_CENTER:
1917 switch (params().align()) {
1918 case LYX_ALIGN_NONE:
1919 case LYX_ALIGN_BLOCK:
1920 case LYX_ALIGN_LAYOUT:
1921 case LYX_ALIGN_SPECIAL:
1923 case LYX_ALIGN_LEFT: {
1925 if (getParLanguage(bparams)->babel() != "hebrew")
1926 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1928 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1929 os << from_ascii(output);
1930 adjust_row_column(output, texrow, column);
1932 } case LYX_ALIGN_RIGHT: {
1934 if (getParLanguage(bparams)->babel() != "hebrew")
1935 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1937 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1938 os << from_ascii(output);
1939 adjust_row_column(output, texrow, column);
1941 } case LYX_ALIGN_CENTER: {
1943 output = corrected_env("\n\\par\\end", "center", ownerCode());
1944 os << from_ascii(output);
1945 adjust_row_column(output, texrow, column);
1954 // This one spits out the text of the paragraph
1955 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1956 BufferParams const & bparams,
1957 Font const & outerfont,
1958 odocstream & os, TexRow & texrow,
1959 OutputParams const & runparams) const
1961 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1963 bool return_value = false;
1967 // well we have to check if we are in an inset with unlimited
1968 // length (all in one row) if that is true then we don't allow
1969 // any special options in the paragraph and also we don't allow
1970 // any environment other than the default layout of the text class
1972 bool asdefault = forceDefaultParagraphs();
1975 style = bparams.getTextClass().defaultLayout();
1980 // Current base font for all inherited font changes, without any
1981 // change caused by an individual character, except for the language:
1982 // It is set to the language of the first character.
1983 // As long as we are in the label, this font is the base font of the
1984 // label. Before the first body character it is set to the base font
1988 // Maybe we have to create a optional argument.
1989 pos_type body_pos = beginOfBody();
1990 unsigned int column = 0;
1993 // the optional argument is kept in curly brackets in
1994 // case it contains a ']'
1997 basefont = getLabelFont(bparams, outerfont);
1999 basefont = getLayoutFont(bparams, outerfont);
2002 // Which font is currently active?
2003 Font running_font(basefont);
2004 // Do we have an open font change?
2005 bool open_font = false;
2007 Change runningChange = Change(Change::UNCHANGED);
2009 texrow.start(id(), 0);
2011 // if the paragraph is empty, the loop will not be entered at all
2013 if (style->isCommand()) {
2018 column += startTeXParParams(bparams, os, texrow,
2019 runparams.moving_arg);
2022 for (pos_type i = 0; i < size(); ++i) {
2023 // First char in paragraph or after label?
2024 if (i == body_pos) {
2027 column += running_font.latexWriteEndChanges(
2028 os, bparams, runparams,
2029 basefont, basefont);
2032 basefont = getLayoutFont(bparams, outerfont);
2033 running_font = basefont;
2035 column += Changes::latexMarkChange(os, bparams,
2036 runningChange, Change(Change::UNCHANGED));
2037 runningChange = Change(Change::UNCHANGED);
2042 if (style->isCommand()) {
2048 column += startTeXParParams(bparams, os,
2050 runparams.moving_arg);
2053 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2054 : pimpl_->lookupChange(i);
2056 if (bparams.outputChanges && runningChange != change) {
2058 column += running_font.latexWriteEndChanges(
2059 os, bparams, runparams, basefont, basefont);
2062 basefont = getLayoutFont(bparams, outerfont);
2063 running_font = basefont;
2065 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2066 runningChange = change;
2069 // do not output text which is marked deleted
2070 // if change tracking output is disabled
2071 if (!bparams.outputChanges && change.type == Change::DELETED) {
2077 value_type const c = getChar(i);
2079 // Fully instantiated font
2080 Font const font = getFont(bparams, i, outerfont);
2082 Font const last_font = running_font;
2084 // Do we need to close the previous font?
2086 (font != running_font ||
2087 font.language() != running_font.language()))
2089 column += running_font.latexWriteEndChanges(
2090 os, bparams, runparams, basefont,
2091 (i == body_pos-1) ? basefont : font);
2092 running_font = basefont;
2096 // Switch file encoding if necessary
2097 if (runparams.encoding->package() == Encoding::inputenc &&
2098 font.language()->encoding()->package() == Encoding::inputenc) {
2099 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2100 runparams.moving_arg, *(runparams.encoding),
2101 *(font.language()->encoding()));
2102 if (enc_switch.first) {
2103 column += enc_switch.second;
2104 runparams.encoding = font.language()->encoding();
2108 // Do we need to change font?
2109 if ((font != running_font ||
2110 font.language() != running_font.language()) &&
2113 odocstringstream ods;
2114 column += font.latexWriteStartChanges(ods, bparams,
2115 runparams, basefont,
2117 running_font = font;
2119 docstring fontchange = ods.str();
2120 // check if the fontchange ends with a trailing blank
2121 // (like "\small " (see bug 3382)
2122 if (suffixIs(fontchange, ' ') && c == ' ')
2123 os << fontchange.substr(0, fontchange.size() - 1)
2124 << from_ascii("{}");
2130 // Do not print the separation of the optional argument
2131 // if style->pass_thru is false. This works because
2132 // simpleTeXSpecialChars ignores spaces if
2133 // style->pass_thru is false.
2134 if (i != body_pos - 1) {
2135 if (pimpl_->simpleTeXBlanks(
2136 *(runparams.encoding), os, texrow,
2137 i, column, font, *style))
2138 // A surrogate pair was output. We
2139 // must not call simpleTeXSpecialChars
2140 // in this iteration, since
2141 // simpleTeXBlanks incremented i, and
2142 // simpleTeXSpecialChars would output
2143 // the combining character again.
2148 OutputParams rp = runparams;
2149 rp.free_spacing = style->free_spacing;
2150 rp.local_font = &font;
2151 rp.intitle = style->intitle;
2152 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2153 texrow, rp, running_font,
2154 basefont, outerfont, open_font,
2155 runningChange, *style, i, column, c);
2157 // Set the encoding to that returned from simpleTeXSpecialChars (see
2158 // comment for encoding member in OutputParams.h)
2159 runparams.encoding = rp.encoding;
2162 // If we have an open font definition, we have to close it
2164 #ifdef FIXED_LANGUAGE_END_DETECTION
2167 .latexWriteEndChanges(os, bparams, runparams,
2169 next_->getFont(bparams, 0, outerfont));
2171 running_font.latexWriteEndChanges(os, bparams,
2172 runparams, basefont, basefont);
2175 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2176 //FIXME: there as we start another \selectlanguage with the next paragraph if
2177 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2178 running_font.latexWriteEndChanges(os, bparams, runparams,
2179 basefont, basefont);
2183 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2185 // Needed if there is an optional argument but no contents.
2186 if (body_pos > 0 && body_pos == size()) {
2188 return_value = false;
2192 column += endTeXParParams(bparams, os, texrow,
2193 runparams.moving_arg);
2196 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2197 return return_value;
2214 string tag_name(PAR_TAG const & pt) {
2216 case PAR_NONE: return "!-- --";
2217 case TT: return "tt";
2218 case SF: return "sf";
2219 case BF: return "bf";
2220 case IT: return "it";
2221 case SL: return "sl";
2222 case EM: return "em";
2229 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2231 p1 = static_cast<PAR_TAG>(p1 | p2);
2236 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2238 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2244 bool Paragraph::emptyTag() const
2246 for (pos_type i = 0; i < size(); ++i) {
2248 Inset const * inset = getInset(i);
2249 Inset::Code lyx_code = inset->lyxCode();
2250 if (lyx_code != Inset::TOC_CODE &&
2251 lyx_code != Inset::INCLUDE_CODE &&
2252 lyx_code != Inset::GRAPHICS_CODE &&
2253 lyx_code != Inset::ERT_CODE &&
2254 lyx_code != Inset::LISTINGS_CODE &&
2255 lyx_code != Inset::FLOAT_CODE &&
2256 lyx_code != Inset::TABULAR_CODE) {
2260 value_type c = getChar(i);
2261 if (c != ' ' && c != '\t')
2269 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2271 for (pos_type i = 0; i < size(); ++i) {
2273 Inset const * inset = getInset(i);
2274 Inset::Code lyx_code = inset->lyxCode();
2275 if (lyx_code == Inset::LABEL_CODE) {
2276 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2277 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2286 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2289 for (i = 0; i < size(); ++i) {
2291 Inset const * inset = getInset(i);
2292 inset->docbook(buf, os, runparams);
2294 value_type c = getChar(i);
2297 os << sgml::escapeChar(c);
2304 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2308 for (pos_type i = initial; i < size(); ++i) {
2309 Font font = getFont(buf.params(), i, outerfont);
2312 if (i != initial && font != font_old)
2321 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2323 OutputParams const & runparams,
2324 Font const & outerfont,
2325 pos_type initial) const
2327 bool emph_flag = false;
2329 Layout_ptr const & style = layout();
2331 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2333 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2336 // parsing main loop
2337 for (pos_type i = initial; i < size(); ++i) {
2338 Font font = getFont(buf.params(), i, outerfont);
2340 // handle <emphasis> tag
2341 if (font_old.emph() != font.emph()) {
2342 if (font.emph() == Font::ON) {
2345 } else if (i != initial) {
2346 os << "</emphasis>";
2352 Inset const * inset = getInset(i);
2353 inset->docbook(buf, os, runparams);
2355 value_type c = getChar(i);
2357 if (style->pass_thru)
2360 os << sgml::escapeChar(c);
2366 os << "</emphasis>";
2369 if (style->free_spacing)
2371 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2376 bool Paragraph::isNewline(pos_type pos) const
2379 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2383 bool Paragraph::isLineSeparator(pos_type pos) const
2385 value_type const c = getChar(pos);
2386 return isLineSeparatorChar(c)
2387 || (c == Paragraph::META_INSET && getInset(pos) &&
2388 getInset(pos)->isLineSeparator());
2392 /// Used by the spellchecker
2393 bool Paragraph::isLetter(pos_type pos) const
2396 return getInset(pos)->isLetter();
2398 value_type const c = getChar(pos);
2399 return isLetterChar(c) || isDigit(c);
2405 Paragraph::getParLanguage(BufferParams const & bparams) const
2408 return getFirstFontSettings(bparams).language();
2409 // FIXME: we should check the prev par as well (Lgb)
2410 return bparams.language;
2414 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2416 return lyxrc.rtl_support
2417 && getParLanguage(bparams)->rightToLeft()
2418 && ownerCode() != Inset::ERT_CODE
2419 && ownerCode() != Inset::LISTINGS_CODE;
2423 void Paragraph::changeLanguage(BufferParams const & bparams,
2424 Language const * from, Language const * to)
2426 // change language including dummy font change at the end
2427 for (pos_type i = 0; i <= size(); ++i) {
2428 Font font = getFontSettings(bparams, i);
2429 if (font.language() == from) {
2430 font.setLanguage(to);
2437 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2439 Language const * doc_language = bparams.language;
2440 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2441 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2443 for (; cit != end; ++cit)
2444 if (cit->font().language() != ignore_language &&
2445 cit->font().language() != latex_language &&
2446 cit->font().language() != doc_language)
2452 // Convert the paragraph to a string.
2453 // Used for building the table of contents
2454 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2456 return asString(buffer, 0, size(), label);
2460 docstring const Paragraph::asString(Buffer const & buffer,
2461 pos_type beg, pos_type end, bool label) const
2464 odocstringstream os;
2466 if (beg == 0 && label && !params().labelString().empty())
2467 os << params().labelString() << ' ';
2469 for (pos_type i = beg; i < end; ++i) {
2470 value_type const c = getChar(i);
2473 else if (c == META_INSET)
2474 getInset(i)->textString(buffer, os);
2481 void Paragraph::setInsetOwner(Inset * inset)
2483 pimpl_->inset_owner = inset;
2487 Change const & Paragraph::lookupChange(pos_type pos) const
2489 BOOST_ASSERT(pos <= size());
2490 return pimpl_->lookupChange(pos);
2494 bool Paragraph::isChanged(pos_type start, pos_type end) const
2496 return pimpl_->isChanged(start, end);
2500 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2502 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2506 void Paragraph::setChange(Change const & change)
2508 pimpl_->setChange(change);
2512 void Paragraph::setChange(pos_type pos, Change const & change)
2514 pimpl_->setChange(pos, change);
2518 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2520 return pimpl_->acceptChanges(bparams, start, end);
2524 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2526 return pimpl_->rejectChanges(bparams, start, end);
2530 int Paragraph::id() const
2536 Layout_ptr const & Paragraph::layout() const
2542 void Paragraph::layout(Layout_ptr const & new_layout)
2544 layout_ = new_layout;
2548 Inset * Paragraph::inInset() const
2550 return pimpl_->inset_owner;
2554 Inset::Code Paragraph::ownerCode() const
2556 return pimpl_->inset_owner
2557 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2561 ParagraphParameters & Paragraph::params()
2563 return pimpl_->params;
2567 ParagraphParameters const & Paragraph::params() const
2569 return pimpl_->params;
2573 bool Paragraph::isFreeSpacing() const
2575 if (layout()->free_spacing)
2578 // for now we just need this, later should we need this in some
2579 // other way we can always add a function to Inset too.
2580 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2584 bool Paragraph::allowEmpty() const
2586 if (layout()->keepempty)
2588 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2592 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2594 if (!Encodings::is_arabic(c))
2597 value_type prev_char = ' ';
2598 value_type next_char = ' ';
2600 for (pos_type i = pos - 1; i >= 0; --i) {
2601 value_type const par_char = getChar(i);
2602 if (!Encodings::isComposeChar_arabic(par_char)) {
2603 prev_char = par_char;
2608 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2609 value_type const par_char = getChar(i);
2610 if (!Encodings::isComposeChar_arabic(par_char)) {
2611 next_char = par_char;
2616 if (Encodings::is_arabic(next_char)) {
2617 if (Encodings::is_arabic(prev_char) &&
2618 !Encodings::is_arabic_special(prev_char))
2619 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2621 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2623 if (Encodings::is_arabic(prev_char) &&
2624 !Encodings::is_arabic_special(prev_char))
2625 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2627 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2632 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2637 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2639 // expand at the end of a row only if there is another hfill on the same row
2640 if (pos == row.endpos() - 1) {
2641 for (pos_type i = row.pos(); i < pos; i++) {
2648 // expand at the beginning of a row only if it is the first row of a paragraph
2649 if (pos == row.pos()) {
2653 // do not expand in some labels
2654 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2657 // if there is anything between the first char of the row and
2658 // the specified position that is neither a newline nor an hfill,
2659 // the hfill will be expanded, otherwise it won't
2660 for (pos_type i = row.pos(); i < pos; i++) {
2661 if (!isNewline(i) && !isHfill(i))
2668 int Paragraph::checkBiblio(bool track_changes)
2671 //This is getting more and more a mess. ...We really should clean
2672 //up this bibitem issue for 1.6. See also bug 2743.
2674 // Add bibitem insets if necessary
2675 if (layout()->labeltype != LABEL_BIBLIO)
2678 bool hasbibitem = !insetlist.empty()
2679 // Insist on it being in pos 0
2680 && getChar(0) == Paragraph::META_INSET
2681 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2686 // remove a bibitem in pos != 0
2687 // restore it later in pos 0 if necessary
2688 // (e.g. if a user inserts contents _before_ the item)
2689 // we're assuming there's only one of these, which there
2691 int erasedInsetPosition = -1;
2692 InsetList::iterator it = insetlist.begin();
2693 InsetList::iterator end = insetlist.end();
2694 for (; it != end; ++it)
2695 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2697 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2698 oldkey = olditem->getParam("key");
2699 oldlabel = olditem->getParam("label");
2700 erasedInsetPosition = it->pos;
2701 eraseChar(erasedInsetPosition, track_changes);
2705 //There was an InsetBibitem at the beginning, and we didn't
2706 //have to erase one.
2707 if (hasbibitem && erasedInsetPosition < 0)
2710 //There was an InsetBibitem at the beginning and we did have to
2711 //erase one. So we give its properties to the beginning inset.
2713 InsetBibitem * inset =
2714 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2715 if (!oldkey.empty())
2716 inset->setParam("key", oldkey);
2717 inset->setParam("label", oldlabel);
2718 return -erasedInsetPosition;
2721 //There was no inset at the beginning, so we need to create one with
2722 //the key and label of the one we erased.
2723 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2724 // restore values of previously deleted item in this par.
2725 if (!oldkey.empty())
2726 inset->setParam("key", oldkey);
2727 inset->setParam("label", oldlabel);
2728 insertInset(0, static_cast<Inset *>(inset),
2729 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2735 void Paragraph::checkAuthors(AuthorList const & authorList)
2737 pimpl_->changes_.checkAuthors(authorList);