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"
31 #include "lyxlength.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/FontMetrics.h"
47 #include "insets/InsetBibitem.h"
48 #include "insets/InsetOptArg.h"
50 #include "support/lstrings.h"
51 #include "support/textutils.h"
52 #include "support/convert.h"
53 #include "support/unicode.h"
55 #include <boost/bind.hpp>
56 #include <boost/next_prior.hpp>
68 using support::contains;
69 using support::rsplit;
72 /////////////////////////////////////////////////////////////////////
76 /////////////////////////////////////////////////////////////////////
82 class Paragraph::Pimpl {
85 Pimpl(Paragraph * owner);
86 /// "Copy constructor"
87 Pimpl(Pimpl const &, Paragraph * owner);
92 /// look up change at given pos
93 Change const & lookupChange(pos_type pos) const;
94 /// is there a change within the given range ?
95 bool isChanged(pos_type start, pos_type end) const;
96 /// will the paragraph be physically merged with the next
97 /// one if the imaginary end-of-par character is logically deleted?
98 bool isMergedOnEndOfParDeletion(bool trackChanges) const;
99 /// set change for the entire par
100 void setChange(Change const & change);
101 /// set change at given pos
102 void setChange(pos_type pos, Change const & change);
103 /// accept changes within the given range
104 void acceptChanges(BufferParams const & bparams, pos_type start, pos_type end);
105 /// reject changes within the given range
106 void rejectChanges(BufferParams const & bparams, pos_type start, pos_type end);
109 value_type getChar(pos_type pos) const;
111 void insertChar(pos_type pos, value_type c, Change const & change);
113 void insertInset(pos_type pos, InsetBase * inset, Change const & change);
114 /// (logically) erase the char at pos; return true if it was actually erased
115 bool eraseChar(pos_type pos, bool trackChanges);
116 /// (logically) erase the given range; return the number of chars actually erased
117 int eraseChars(pos_type start, pos_type end, bool trackChanges);
119 InsetBase * inset_owner;
121 /** A font entry covers a range of positions. Notice that the
122 entries in the list are inserted in random order.
123 I don't think it's worth the effort to implement a more effective
124 datastructure, because the number of different fonts in a paragraph
126 Nevertheless, I decided to store fontlist using a sorted vector:
127 fontlist = { {pos_1,font_1} , {pos_2,font_2} , ... } where
128 pos_1 < pos_2 < ..., font_{i-1} != font_i for all i,
129 and font_i covers the chars in positions pos_{i-1}+1,...,pos_i
130 (font_1 covers the chars 0,...,pos_1) (Dekel)
135 FontTable(pos_type p, LyXFont const & f)
139 pos_type pos() const { return pos_; }
141 void pos(pos_type p) { pos_ = p; }
143 LyXFont const & font() const { return font_; }
145 void font(LyXFont const & f) { font_ = f;}
147 /// End position of paragraph this font attribute covers
149 /** Font. Interpretation of the font values:
150 If a value is LyXFont::INHERIT_*, it means that the font
151 attribute is inherited from either the layout of this
152 paragraph or, in the case of nested paragraphs, from the
153 layout in the environment one level up until completely
155 The values LyXFont::IGNORE_* and LyXFont::TOGGLE are NOT
156 allowed in these font tables.
161 friend class matchFT;
165 /// used by lower_bound and upper_bound
166 int operator()(FontTable const & a, FontTable const & b) const {
167 return a.pos() < b.pos();
172 typedef std::vector<FontTable> FontList;
176 /// Output the surrogate pair formed by \p c and \p next to \p os.
177 /// \return the number of characters written.
178 int latexSurrogatePair(odocstream & os, value_type c, value_type next,
180 /// Output a space in appropriate formatting (or a surrogate pair
181 /// if the next character is a combining character).
182 /// \return whether a surrogate pair was output.
183 bool simpleTeXBlanks(Encoding const &,
184 odocstream &, TexRow & texrow,
186 unsigned int & column,
187 LyXFont const & font,
188 LyXLayout const & style);
190 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
192 TexRow & texrow, OutputParams const &,
193 LyXFont & running_font,
195 LyXFont const & outerfont,
197 Change::Type & running_change,
198 LyXLayout const & style,
200 unsigned int & column, value_type const c);
203 void validate(LaTeXFeatures & features,
204 LyXLayout const & layout) const;
209 static unsigned int paragraph_id;
211 ParagraphParameters params;
215 pos_type size() const { return owner_->size(); }
216 /// match a string against a particular point in the paragraph
217 bool isTextAt(std::string const & str, pos_type pos) const;
219 /// for recording and looking up changes
230 using std::upper_bound;
231 using std::lower_bound;
235 // Initialization of the counter for the paragraph id's,
236 unsigned int Paragraph::Pimpl::paragraph_id = 0;
240 struct special_phrase {
246 special_phrase const special_phrases[] = {
247 { "LyX", from_ascii("\\LyX{}"), false },
248 { "TeX", from_ascii("\\TeX{}"), true },
249 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
250 { "LaTeX", from_ascii("\\LaTeX{}"), true },
253 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
258 Paragraph::Pimpl::Pimpl(Paragraph * owner)
262 id_ = paragraph_id++;
266 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
267 : params(p.params), changes_(p.changes_), owner_(owner)
269 inset_owner = p.inset_owner;
270 fontlist = p.fontlist;
271 id_ = paragraph_id++;
275 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
277 BOOST_ASSERT(start >= 0 && start <= size());
278 BOOST_ASSERT(end > start && end <= size() + 1);
280 return changes_.isChanged(start, end);
284 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
285 // keep the logic here in sync with the logic of eraseChars()
291 Change change = changes_.lookup(size());
293 return change.type == Change::INSERTED && change.author == 0;
297 void Paragraph::Pimpl::setChange(Change const & change)
299 // beware of the imaginary end-of-par character!
300 changes_.set(change, 0, size() + 1);
303 * Propagate the change recursively - but not in case of DELETED!
305 * Imagine that your co-author makes changes in an existing inset. He
306 * sends your document to you and you come to the conclusion that the
307 * inset should go completely. If you erase it, LyX must not delete all
308 * text within the inset. Otherwise, the change tracked insertions of
309 * your co-author get lost and there is no way to restore them later.
311 * Conclusion: An inset's content should remain untouched if you delete it
314 if (change.type != Change::DELETED) {
315 for (pos_type pos = 0; pos < size(); ++pos) {
316 if (owner_->isInset(pos)) {
317 owner_->getInset(pos)->setChange(change);
324 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
326 BOOST_ASSERT(pos >= 0 && pos <= size());
328 changes_.set(change, pos);
330 // see comment in setChange(Change const &) above
332 if (change.type != Change::DELETED &&
333 pos < size() && owner_->isInset(pos)) {
334 owner_->getInset(pos)->setChange(change);
339 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
341 BOOST_ASSERT(pos >= 0 && pos <= size());
343 return changes_.lookup(pos);
347 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
349 BOOST_ASSERT(start >= 0 && start <= size());
350 BOOST_ASSERT(end > start && end <= size() + 1);
352 for (pos_type pos = start; pos < end; ++pos) {
353 switch (lookupChange(pos).type) {
354 case Change::UNCHANGED:
355 // accept changes in nested inset
356 if (pos < size() && owner_->isInset(pos)) {
357 owner_->getInset(pos)->acceptChanges(bparams);
362 case Change::INSERTED:
363 changes_.set(Change(Change::UNCHANGED), pos);
364 // also accept changes in nested inset
365 if (pos < size() && owner_->isInset(pos)) {
366 owner_->getInset(pos)->acceptChanges(bparams);
370 case Change::DELETED:
371 // Suppress access to non-existent
372 // "end-of-paragraph char"
374 eraseChar(pos, false);
385 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
387 BOOST_ASSERT(start >= 0 && start <= size());
388 BOOST_ASSERT(end > start && end <= size() + 1);
390 for (pos_type pos = start; pos < end; ++pos) {
391 switch (lookupChange(pos).type) {
392 case Change::UNCHANGED:
393 // reject changes in nested inset
394 if (pos < size() && owner_->isInset(pos)) {
395 owner_->getInset(pos)->rejectChanges(bparams);
399 case Change::INSERTED:
400 // Suppress access to non-existent
401 // "end-of-paragraph char"
403 eraseChar(pos, false);
409 case Change::DELETED:
410 changes_.set(Change(Change::UNCHANGED), pos);
412 // Do NOT reject changes within a deleted inset!
413 // There may be insertions of a co-author inside of it!
421 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
423 BOOST_ASSERT(pos >= 0 && pos <= size());
425 return owner_->getChar(pos);
429 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
431 BOOST_ASSERT(pos >= 0 && pos <= size());
434 changes_.insert(change, pos);
436 // This is actually very common when parsing buffers (and
437 // maybe inserting ascii text)
439 // when appending characters, no need to update tables
440 owner_->text_.push_back(c);
444 owner_->text_.insert(owner_->text_.begin() + pos, c);
446 // Update the font table.
447 FontTable search_font(pos, LyXFont());
448 for (FontList::iterator it
449 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
450 it != fontlist.end(); ++it)
452 it->pos(it->pos() + 1);
456 owner_->insetlist.increasePosAfterPos(pos);
460 void Paragraph::Pimpl::insertInset(pos_type pos, InsetBase * inset,
461 Change const & change)
464 BOOST_ASSERT(pos >= 0 && pos <= size());
466 insertChar(pos, META_INSET, change);
467 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
469 // Add a new entry in the insetlist.
470 owner_->insetlist.insert(inset, pos);
474 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
476 BOOST_ASSERT(pos >= 0 && pos <= size());
478 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
481 Change change = changes_.lookup(pos);
483 // set the character to DELETED if
484 // a) it was previously unchanged or
485 // b) it was inserted by a co-author
487 if (change.type == Change::UNCHANGED ||
488 (change.type == Change::INSERTED && change.author != 0)) {
489 setChange(pos, Change(Change::DELETED));
493 if (change.type == Change::DELETED)
497 // Don't physically access the imaginary end-of-paragraph character.
498 // eraseChar() can only mark it as DELETED. A physical deletion of
499 // end-of-par must be handled externally.
507 // if it is an inset, delete the inset entry
508 if (owner_->text_[pos] == Paragraph::META_INSET) {
509 owner_->insetlist.erase(pos);
512 owner_->text_.erase(owner_->text_.begin() + pos);
514 // Erase entries in the tables.
515 FontTable search_font(pos, LyXFont());
517 FontList::iterator it =
518 lower_bound(fontlist.begin(),
520 search_font, matchFT());
521 if (it != fontlist.end() && it->pos() == pos &&
523 (it != fontlist.begin()
524 && boost::prior(it)->pos() == pos - 1))) {
525 // If it is a multi-character font
526 // entry, we just make it smaller
527 // (see update below), otherwise we
529 unsigned int const i = it - fontlist.begin();
530 fontlist.erase(fontlist.begin() + i);
531 it = fontlist.begin() + i;
532 if (i > 0 && i < fontlist.size() &&
533 fontlist[i - 1].font() == fontlist[i].font()) {
534 fontlist.erase(fontlist.begin() + i - 1);
535 it = fontlist.begin() + i - 1;
539 // Update all other entries
540 FontList::iterator fend = fontlist.end();
541 for (; it != fend; ++it)
542 it->pos(it->pos() - 1);
544 // Update the insetlist
545 owner_->insetlist.decreasePosAfterPos(pos);
551 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
553 BOOST_ASSERT(start >= 0 && start <= size());
554 BOOST_ASSERT(end >= start && end <= size() + 1);
557 for (pos_type count = end - start; count; --count) {
558 if (!eraseChar(i, trackChanges))
565 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
566 value_type next, Encoding const & encoding)
568 // Writing next here may circumvent a possible font change between
569 // c and next. Since next is only output if it forms a surrogate pair
570 // with c we can ignore this:
571 // A font change inside a surrogate pair does not make sense and is
572 // hopefully impossible to input.
573 // FIXME: change tracking
574 // Is this correct WRT change tracking?
575 docstring const latex1 = encoding.latexChar(next);
576 docstring const latex2 = encoding.latexChar(c);
577 os << latex1 << '{' << latex2 << '}';
578 return latex1.length() + latex2.length() + 2;
582 bool Paragraph::Pimpl::simpleTeXBlanks(Encoding const & encoding,
583 odocstream & os, TexRow & texrow,
585 unsigned int & column,
586 LyXFont const & font,
587 LyXLayout const & style)
592 if (i < size() - 1) {
593 char_type next = getChar(i + 1);
594 if (Encodings::isCombiningChar(next)) {
595 // This space has an accent, so we must always output it.
596 column += latexSurrogatePair(os, ' ', next, encoding) - 1;
602 if (lyxrc.plaintext_linelen > 0
603 && column > lyxrc.plaintext_linelen
605 && getChar(i - 1) != ' '
607 // same in FreeSpacing mode
608 && !owner_->isFreeSpacing()
609 // In typewriter mode, we want to avoid
610 // ! . ? : at the end of a line
611 && !(font.family() == LyXFont::TYPEWRITER_FAMILY
612 && (getChar(i - 1) == '.'
613 || getChar(i - 1) == '?'
614 || getChar(i - 1) == ':'
615 || getChar(i - 1) == '!'))) {
618 texrow.start(owner_->id(), i + 1);
620 } else if (style.free_spacing) {
629 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
631 pos_type const len = str.length();
633 // is the paragraph large enough?
634 if (pos + len > size())
637 // does the wanted text start at point?
638 for (string::size_type i = 0; i < str.length(); ++i) {
639 // Caution: direct comparison of characters works only
640 // because str is pure ASCII.
641 if (str[i] != owner_->text_[pos + i])
645 // is there a font change in middle of the word?
646 FontList::const_iterator cit = fontlist.begin();
647 FontList::const_iterator end = fontlist.end();
648 for (; cit != end; ++cit) {
649 if (cit->pos() >= pos)
652 if (cit != end && pos + len - 1 > cit->pos())
659 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
660 BufferParams const & bparams,
663 OutputParams const & runparams,
664 LyXFont & running_font,
666 LyXFont const & outerfont,
668 Change::Type & running_change,
669 LyXLayout const & style,
671 unsigned int & column,
674 if (style.pass_thru) {
675 if (c != Paragraph::META_INSET) {
677 // FIXME UNICODE: This can fail if c cannot
678 // be encoded in the current encoding.
681 owner_->getInset(i)->plaintext(buf, os, runparams);
685 // Two major modes: LaTeX or plain
686 // Handle here those cases common to both modes
687 // and then split to handle the two modes separately.
689 case Paragraph::META_INSET: {
690 InsetBase * inset = owner_->getInset(i);
692 // FIXME: remove this check
696 // FIXME: move this to InsetNewline::latex
697 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
698 // newlines are handled differently here than
699 // the default in simpleTeXSpecialChars().
700 if (!style.newline_allowed) {
704 column += running_font.latexWriteEndChanges(
705 os, basefont, basefont);
709 if (running_font.family() == LyXFont::TYPEWRITER_FAMILY)
712 basefont = owner_->getLayoutFont(bparams, outerfont);
713 running_font = basefont;
715 if (runparams.moving_arg)
721 texrow.start(owner_->id(), i + 1);
726 // output change tracking marks only if desired,
727 // if dvipost is installed,
728 // and with dvi/ps (other formats don't work)
729 LaTeXFeatures features(buf, bparams, runparams);
730 bool const output = bparams.outputChanges
731 && runparams.flavor == OutputParams::LATEX
732 && features.isAvailable("dvipost");
734 if (inset->canTrackChanges()) {
735 column += Changes::latexMarkChange(os, running_change,
736 Change::UNCHANGED, output);
737 running_change = Change::UNCHANGED;
741 odocstream::pos_type const len = os.tellp();
743 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
744 || inset->lyxCode() == InsetBase::MATH_CODE
745 || inset->lyxCode() == InsetBase::URL_CODE)
746 && running_font.isRightToLeft()) {
752 #warning Bug: we can have an empty font change here!
753 // if there has just been a font change, we are going to close it
754 // right now, which means stupid latex code like \textsf{}. AFAIK,
755 // this does not harm dvi output. A minor bug, thus (JMarc)
757 // some insets cannot be inside a font change command
758 if (open_font && inset->noFontChange()) {
759 column += running_font.latexWriteEndChanges(
760 os, basefont, basefont);
762 basefont = owner_->getLayoutFont(bparams, outerfont);
763 running_font = basefont;
766 int tmp = inset->latex(buf, os, runparams);
772 for (int j = 0; j < tmp; ++j) {
775 texrow.start(owner_->id(), i + 1);
778 column += os.tellp() - len;
784 // And now for the special cases within each mode
788 os << "\\textbackslash{}";
792 // The following characters could be written literally in latin1, but they
793 // would be wrongly converted on systems where char is signed, so we give
795 // This also makes us independant from the encoding of this source file.
796 case '|': case '<': case '>':
797 // In T1 encoding, these characters exist
798 if (lyxrc.fontenc == "T1") {
800 //... but we should avoid ligatures
801 if ((c == '>' || c == '<')
803 && getChar(i + 1) == c) {
804 //os << "\\textcompwordmark{}";
806 // Jean-Marc, have a look at
807 // this. I think this works
815 // Typewriter font also has them
816 if (running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
820 // Otherwise, we use what LaTeX
824 os << "\\textless{}";
828 os << "\\textgreater{}";
838 case '-': // "--" in Typewriter mode -> "-{}-"
839 if (i <= size() - 2 &&
840 getChar(i + 1) == '-' &&
841 running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
850 os << "\\char`\\\"{}";
855 case '%': case '#': case '{':
863 os << "\\textasciitilde{}";
868 os << "\\textasciicircum{}";
873 // avoid being mistaken for optional arguments
881 // Blanks are printed before font switching.
882 // Sure? I am not! (try nice-latex)
883 // I am sure it's correct. LyX might be smarter
884 // in the future, but for now, nothing wrong is
890 // I assume this is hack treating typewriter as verbatim
891 // FIXME UNICODE: This can fail if c cannot be encoded
892 // in the current encoding.
893 if (running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
902 // FIXME: if we have "LaTeX" with a font
903 // change in the middle (before the 'T', then
904 // the "TeX" part is still special cased.
905 // Really we should only operate this on
906 // "words" for some definition of word
910 for (; pnr < phrases_nr; ++pnr) {
911 if (isTextAt(special_phrases[pnr].phrase, i)) {
912 os << special_phrases[pnr].macro;
913 i += special_phrases[pnr].phrase.length() - 1;
914 column += special_phrases[pnr].macro.length() - 1;
919 if (pnr == phrases_nr && c != '\0') {
920 Encoding const & encoding = *(runparams.encoding);
921 if (i < size() - 1) {
922 char_type next = getChar(i + 1);
923 if (Encodings::isCombiningChar(next)) {
924 column += latexSurrogatePair(os, c, next, encoding) - 1;
929 docstring const latex = encoding.latexChar(c);
930 if (latex.length() > 1 &&
931 latex[latex.length() - 1] != '}') {
932 // Prevent eating of a following
933 // space or command corruption by
934 // following characters
935 column += latex.length() + 1;
938 column += latex.length() - 1;
948 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
949 LyXLayout const & layout) const
951 BufferParams const & bparams = features.bufferParams();
954 if (!params.spacing().isDefault())
955 features.require("setspace");
958 features.useLayout(layout.name());
961 Language const * doc_language = bparams.language;
963 FontList::const_iterator fcit = fontlist.begin();
964 FontList::const_iterator fend = fontlist.end();
965 for (; fcit != fend; ++fcit) {
966 if (fcit->font().noun() == LyXFont::ON) {
967 LYXERR(Debug::LATEX) << "font.noun: "
968 << fcit->font().noun()
970 features.require("noun");
971 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
972 << to_utf8(fcit->font().stateText(0))
975 switch (fcit->font().color()) {
977 case LColor::inherit:
979 // probably we should put here all interface colors used for
980 // font displaying! For now I just add this ones I know of (Jug)
985 features.require("color");
986 LYXERR(Debug::LATEX) << "Color enabled. Font: "
987 << to_utf8(fcit->font().stateText(0))
991 Language const * language = fcit->font().language();
992 if (language->babel() != doc_language->babel() &&
993 language != ignore_language &&
994 language != latex_language)
996 features.useLanguage(language);
997 LYXERR(Debug::LATEX) << "Found language "
998 << language->babel() << endl;
1002 if (!params.leftIndent().zero())
1003 features.require("ParagraphLeftIndent");
1006 InsetList::const_iterator icit = owner_->insetlist.begin();
1007 InsetList::const_iterator iend = owner_->insetlist.end();
1008 for (; icit != iend; ++icit) {
1010 icit->inset->validate(features);
1011 if (layout.needprotect &&
1012 icit->inset->lyxCode() == InsetBase::FOOT_CODE)
1013 features.require("NeedLyXFootnoteCode");
1017 // then the contents
1018 for (pos_type i = 0; i < size() ; ++i) {
1019 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1020 if (!special_phrases[pnr].builtin
1021 && isTextAt(special_phrases[pnr].phrase, i)) {
1022 features.require(special_phrases[pnr].phrase);
1026 Encodings::validate(getChar(i), features);
1034 /////////////////////////////////////////////////////////////////////
1038 /////////////////////////////////////////////////////////////////////
1042 Paragraph::Paragraph()
1043 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1050 Paragraph::Paragraph(Paragraph const & par)
1051 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1052 layout_(par.layout_),
1053 text_(par.text_), begin_of_body_(par.begin_of_body_),
1054 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1056 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1057 InsetList::iterator it = insetlist.begin();
1058 InsetList::iterator end = insetlist.end();
1059 for (; it != end; ++it)
1060 it->inset = it->inset->clone().release();
1064 Paragraph & Paragraph::operator=(Paragraph const & par)
1066 // needed as we will destroy the pimpl_ before copying it
1068 itemdepth = par.itemdepth;
1070 insetlist = par.insetlist;
1071 InsetList::iterator it = insetlist.begin();
1072 InsetList::iterator end = insetlist.end();
1073 for (; it != end; ++it)
1074 it->inset = it->inset->clone().release();
1076 layout_ = par.layout();
1078 begin_of_body_ = par.begin_of_body_;
1081 pimpl_ = new Pimpl(*par.pimpl_, this);
1087 Paragraph::~Paragraph()
1091 //lyxerr << "Paragraph::paragraph_id = "
1092 // << Paragraph::paragraph_id << endl;
1096 void Paragraph::write(Buffer const & buf, ostream & os,
1097 BufferParams const & bparams,
1098 depth_type & dth) const
1100 // The beginning or end of a deeper (i.e. nested) area?
1101 if (dth != params().depth()) {
1102 if (params().depth() > dth) {
1103 while (params().depth() > dth) {
1104 os << "\n\\begin_deeper";
1108 while (params().depth() < dth) {
1109 os << "\n\\end_deeper";
1115 // First write the layout
1116 os << "\n\\begin_layout " << layout()->name() << '\n';
1120 LyXFont font1(LyXFont::ALL_INHERIT, bparams.language);
1122 Change running_change = Change(Change::UNCHANGED);
1125 for (pos_type i = 0; i <= size(); ++i) {
1127 Change change = pimpl_->lookupChange(i);
1128 Changes::lyxMarkChange(os, column, running_change, change);
1129 running_change = change;
1134 // Write font changes
1135 LyXFont font2 = getFontSettings(bparams, i);
1136 if (font2 != font1) {
1137 font2.lyxWriteChanges(font1, os);
1142 value_type const c = getChar(i);
1146 InsetBase const * inset = getInset(i);
1148 if (inset->directWrite()) {
1149 // international char, let it write
1150 // code directly so it's shorter in
1152 inset->write(buf, os);
1156 os << "\\begin_inset ";
1157 inset->write(buf, os);
1158 os << "\n\\end_inset\n\n";
1164 os << "\n\\backslash\n";
1168 if (i + 1 < size() && getChar(i + 1) == ' ') {
1175 if ((column > 70 && c == ' ')
1180 // this check is to amend a bug. LyX sometimes
1181 // inserts '\0' this could cause problems.
1183 std::vector<char> tmp = ucs4_to_utf8(c);
1184 tmp.push_back('\0');
1187 lyxerr << "ERROR (Paragraph::writeFile):"
1188 " NULL char in structure." << endl;
1194 os << "\n\\end_layout\n";
1198 void Paragraph::validate(LaTeXFeatures & features) const
1200 pimpl_->validate(features, *layout());
1204 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1206 return pimpl_->eraseChar(pos, trackChanges);
1210 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1212 return pimpl_->eraseChars(start, end, trackChanges);
1216 void Paragraph::insert(pos_type start, docstring const & str,
1217 LyXFont const & font, Change const & change)
1219 for (size_t i = 0, n = str.size(); i != n ; ++i)
1220 insertChar(start + i, str[i], font, change);
1224 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1227 pimpl_->insertChar(pos, c, Change(trackChanges ?
1228 Change::INSERTED : Change::UNCHANGED));
1232 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1233 LyXFont const & font, bool trackChanges)
1235 pimpl_->insertChar(pos, c, Change(trackChanges ?
1236 Change::INSERTED : Change::UNCHANGED));
1241 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1242 LyXFont const & font, Change const & change)
1244 pimpl_->insertChar(pos, c, change);
1249 void Paragraph::insertInset(pos_type pos, InsetBase * inset,
1250 Change const & change)
1252 pimpl_->insertInset(pos, inset, change);
1256 void Paragraph::insertInset(pos_type pos, InsetBase * inset,
1257 LyXFont const & font, Change const & change)
1259 pimpl_->insertInset(pos, inset, change);
1264 bool Paragraph::insetAllowed(InsetBase_code code)
1266 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1270 // Gets uninstantiated font setting at position.
1271 LyXFont const Paragraph::getFontSettings(BufferParams const & bparams,
1275 lyxerr << " pos: " << pos << " size: " << size() << endl;
1276 BOOST_ASSERT(pos <= size());
1279 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1280 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1281 for (; cit != end; ++cit)
1282 if (cit->pos() >= pos)
1288 if (pos == size() && !empty())
1289 return getFontSettings(bparams, pos - 1);
1291 return LyXFont(LyXFont::ALL_INHERIT, getParLanguage(bparams));
1295 FontSpan Paragraph::fontSpan(pos_type pos) const
1297 BOOST_ASSERT(pos <= size());
1300 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1301 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1302 for (; cit != end; ++cit) {
1303 if (cit->pos() >= pos) {
1304 if (pos >= beginOfBody())
1305 return FontSpan(std::max(start, beginOfBody()),
1308 return FontSpan(start,
1309 std::min(beginOfBody() - 1,
1312 start = cit->pos() + 1;
1315 // This should not happen, but if so, we take no chances.
1316 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1318 return FontSpan(pos, pos);
1322 // Gets uninstantiated font setting at position 0
1323 LyXFont const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1325 if (!empty() && !pimpl_->fontlist.empty())
1326 return pimpl_->fontlist[0].font();
1328 return LyXFont(LyXFont::ALL_INHERIT, bparams.language);
1332 // Gets the fully instantiated font at a given position in a paragraph
1333 // This is basically the same function as LyXText::GetFont() in text2.C.
1334 // The difference is that this one is used for generating the LaTeX file,
1335 // and thus cosmetic "improvements" are disallowed: This has to deliver
1336 // the true picture of the buffer. (Asger)
1337 LyXFont const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1338 LyXFont const & outerfont) const
1340 BOOST_ASSERT(pos >= 0);
1342 LyXLayout_ptr const & lout = layout();
1344 pos_type const body_pos = beginOfBody();
1348 layoutfont = lout->labelfont;
1350 layoutfont = lout->font;
1352 LyXFont font = getFontSettings(bparams, pos);
1353 font.realize(layoutfont);
1354 font.realize(outerfont);
1355 font.realize(bparams.getFont());
1361 LyXFont const Paragraph::getLabelFont
1362 (BufferParams const & bparams, LyXFont const & outerfont) const
1364 LyXFont tmpfont = layout()->labelfont;
1365 tmpfont.setLanguage(getParLanguage(bparams));
1366 tmpfont.realize(outerfont);
1367 tmpfont.realize(bparams.getFont());
1372 LyXFont const Paragraph::getLayoutFont
1373 (BufferParams const & bparams, LyXFont const & outerfont) const
1375 LyXFont tmpfont = layout()->font;
1376 tmpfont.setLanguage(getParLanguage(bparams));
1377 tmpfont.realize(outerfont);
1378 tmpfont.realize(bparams.getFont());
1383 /// Returns the height of the highest font in range
1384 LyXFont_size Paragraph::highestFontInRange
1385 (pos_type startpos, pos_type endpos, LyXFont_size def_size) const
1387 if (pimpl_->fontlist.empty())
1390 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1391 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1392 for (; end_it != end; ++end_it) {
1393 if (end_it->pos() >= endpos)
1400 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1401 for (; cit != end; ++cit) {
1402 if (cit->pos() >= startpos)
1406 LyXFont::FONT_SIZE maxsize = LyXFont::SIZE_TINY;
1407 for (; cit != end_it; ++cit) {
1408 LyXFont::FONT_SIZE size = cit->font().size();
1409 if (size == LyXFont::INHERIT_SIZE)
1411 if (size > maxsize && size <= LyXFont::SIZE_HUGER)
1418 Paragraph::value_type
1419 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1421 value_type c = getChar(pos);
1422 if (!lyxrc.rtl_support)
1452 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1459 void Paragraph::setFont(pos_type pos, LyXFont const & font)
1461 BOOST_ASSERT(pos <= size());
1463 // First, reduce font against layout/label font
1464 // Update: The setCharFont() routine in text2.C already
1465 // reduces font, so we don't need to do that here. (Asger)
1466 // No need to simplify this because it will disappear
1467 // in a new kernel. (Asger)
1468 // Next search font table
1470 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1471 Pimpl::FontList::iterator it = beg;
1472 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1473 for (; it != endit; ++it) {
1474 if (it->pos() >= pos)
1477 size_t const i = distance(beg, it);
1478 bool notfound = (it == endit);
1480 if (!notfound && pimpl_->fontlist[i].font() == font)
1483 bool begin = pos == 0 || notfound ||
1484 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1485 // Is position pos is a beginning of a font block?
1486 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1487 // Is position pos is the end of a font block?
1488 if (begin && end) { // A single char block
1489 if (i + 1 < pimpl_->fontlist.size() &&
1490 pimpl_->fontlist[i + 1].font() == font) {
1491 // Merge the singleton block with the next block
1492 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1493 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1494 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1495 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1496 // Merge the singleton block with the previous block
1497 pimpl_->fontlist[i - 1].pos(pos);
1498 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1500 pimpl_->fontlist[i].font(font);
1502 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1503 pimpl_->fontlist[i - 1].pos(pos);
1505 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1506 Pimpl::FontTable(pos, font));
1508 pimpl_->fontlist[i].pos(pos - 1);
1509 if (!(i + 1 < pimpl_->fontlist.size() &&
1510 pimpl_->fontlist[i + 1].font() == font))
1511 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1512 Pimpl::FontTable(pos, font));
1513 } else { // The general case. The block is splitted into 3 blocks
1514 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1515 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1516 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1517 Pimpl::FontTable(pos, font));
1522 void Paragraph::makeSameLayout(Paragraph const & par)
1524 layout(par.layout());
1526 params() = par.params();
1530 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1532 if (isFreeSpacing())
1538 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1539 if (eraseChar(pos, trackChanges))
1545 return count > 0 || pos > 0;
1549 bool Paragraph::hasSameLayout(Paragraph const & par) const
1551 return par.layout() == layout() && params().sameLayout(par.params());
1555 depth_type Paragraph::getDepth() const
1557 return params().depth();
1561 depth_type Paragraph::getMaxDepthAfter() const
1563 if (layout()->isEnvironment())
1564 return params().depth() + 1;
1566 return params().depth();
1570 char Paragraph::getAlign() const
1572 if (params().align() == LYX_ALIGN_LAYOUT)
1573 return layout()->align;
1575 return params().align();
1579 docstring const & Paragraph::getLabelstring() const
1581 return params().labelString();
1585 // the next two functions are for the manual labels
1586 docstring const Paragraph::getLabelWidthString() const
1588 if (!params().labelWidthString().empty())
1589 return params().labelWidthString();
1591 return _("Senseless with this layout!");
1595 void Paragraph::setLabelWidthString(docstring const & s)
1597 params().labelWidthString(s);
1601 docstring const Paragraph::translateIfPossible(docstring const & s,
1602 BufferParams const & bparams) const
1604 if (!support::isAscii(s) || s.empty()) {
1605 // This must be a user defined layout. We cannot translate
1606 // this, since gettext accepts only ascii keys.
1609 // Probably standard layout, try to translate
1610 Messages & m = getMessages(getParLanguage(bparams)->code());
1611 return m.get(to_ascii(s));
1615 docstring Paragraph::expandLabel(LyXLayout_ptr const & layout,
1616 BufferParams const & bparams, bool process_appendix) const
1618 LyXTextClass const & tclass = bparams.getLyXTextClass();
1621 if (process_appendix && params().appendix())
1622 fmt = translateIfPossible(layout->labelstring_appendix(),
1625 fmt = translateIfPossible(layout->labelstring(), bparams);
1627 // handle 'inherited level parts' in 'fmt',
1628 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1629 size_t const i = fmt.find('@', 0);
1630 if (i != docstring::npos) {
1631 size_t const j = fmt.find('@', i + 1);
1632 if (j != docstring::npos) {
1633 docstring parent(fmt, i + 1, j - i - 1);
1635 docstring label = expandLabel(tclass[to_utf8(parent)], bparams);
1636 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1640 return tclass.counters().counterLabel(fmt);
1644 void Paragraph::applyLayout(LyXLayout_ptr const & new_layout)
1647 params().labelWidthString(docstring());
1648 params().align(LYX_ALIGN_LAYOUT);
1649 params().spacing(Spacing(Spacing::Default));
1653 pos_type Paragraph::beginOfBody() const
1655 return begin_of_body_;
1659 void Paragraph::setBeginOfBody()
1661 if (layout()->labeltype != LABEL_MANUAL) {
1666 // Unroll the first two cycles of the loop
1667 // and remember the previous character to
1668 // remove unnecessary getChar() calls
1670 pos_type end = size();
1671 if (i < end && !isNewline(i)) {
1673 char_type previous_char = 0;
1676 previous_char = text_[i];
1677 if (!isNewline(i)) {
1679 while (i < end && previous_char != ' ') {
1684 previous_char = temp;
1694 // returns -1 if inset not found
1695 int Paragraph::getPositionOfInset(InsetBase const * inset) const
1698 InsetList::const_iterator it = insetlist.begin();
1699 InsetList::const_iterator end = insetlist.end();
1700 for (; it != end; ++it)
1701 if (it->inset == inset)
1707 InsetBibitem * Paragraph::bibitem() const
1709 if (!insetlist.empty()) {
1710 InsetBase * inset = insetlist.begin()->inset;
1711 if (inset->lyxCode() == InsetBase::BIBITEM_CODE)
1712 return static_cast<InsetBibitem *>(inset);
1718 bool Paragraph::forceDefaultParagraphs() const
1720 return inInset() && inInset()->forceDefaultParagraphs(0);
1726 // paragraphs inside floats need different alignment tags to avoid
1729 bool noTrivlistCentering(InsetBase::Code code)
1731 return code == InsetBase::FLOAT_CODE || code == InsetBase::WRAP_CODE;
1735 string correction(string const & orig)
1737 if (orig == "flushleft")
1738 return "raggedright";
1739 if (orig == "flushright")
1740 return "raggedleft";
1741 if (orig == "center")
1747 string const corrected_env(string const & suffix, string const & env,
1748 InsetBase::Code code)
1750 string output = suffix + "{";
1751 if (noTrivlistCentering(code))
1752 output += correction(env);
1756 if (suffix == "\\begin")
1762 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1764 if (!contains(str, "\n"))
1765 column += str.size();
1769 column = rsplit(str, tmp, '\n').size();
1776 // This could go to ParagraphParameters if we want to
1777 int Paragraph::startTeXParParams(BufferParams const & bparams,
1778 odocstream & os, TexRow & texrow,
1779 bool moving_arg) const
1783 if (params().noindent()) {
1784 os << "\\noindent ";
1788 switch (params().align()) {
1789 case LYX_ALIGN_NONE:
1790 case LYX_ALIGN_BLOCK:
1791 case LYX_ALIGN_LAYOUT:
1792 case LYX_ALIGN_SPECIAL:
1794 case LYX_ALIGN_LEFT:
1795 case LYX_ALIGN_RIGHT:
1796 case LYX_ALIGN_CENTER:
1804 switch (params().align()) {
1805 case LYX_ALIGN_NONE:
1806 case LYX_ALIGN_BLOCK:
1807 case LYX_ALIGN_LAYOUT:
1808 case LYX_ALIGN_SPECIAL:
1810 case LYX_ALIGN_LEFT: {
1812 if (getParLanguage(bparams)->babel() != "hebrew")
1813 output = corrected_env("\\begin", "flushleft", ownerCode());
1815 output = corrected_env("\\begin", "flushright", ownerCode());
1816 os << from_ascii(output);
1817 adjust_row_column(output, texrow, column);
1819 } case LYX_ALIGN_RIGHT: {
1821 if (getParLanguage(bparams)->babel() != "hebrew")
1822 output = corrected_env("\\begin", "flushright", ownerCode());
1824 output = corrected_env("\\begin", "flushleft", ownerCode());
1825 os << from_ascii(output);
1826 adjust_row_column(output, texrow, column);
1828 } case LYX_ALIGN_CENTER: {
1830 output = corrected_env("\\begin", "center", ownerCode());
1831 os << from_ascii(output);
1832 adjust_row_column(output, texrow, column);
1841 // This could go to ParagraphParameters if we want to
1842 int Paragraph::endTeXParParams(BufferParams const & bparams,
1843 odocstream & os, TexRow & texrow,
1844 bool moving_arg) const
1848 switch (params().align()) {
1849 case LYX_ALIGN_NONE:
1850 case LYX_ALIGN_BLOCK:
1851 case LYX_ALIGN_LAYOUT:
1852 case LYX_ALIGN_SPECIAL:
1854 case LYX_ALIGN_LEFT:
1855 case LYX_ALIGN_RIGHT:
1856 case LYX_ALIGN_CENTER:
1864 switch (params().align()) {
1865 case LYX_ALIGN_NONE:
1866 case LYX_ALIGN_BLOCK:
1867 case LYX_ALIGN_LAYOUT:
1868 case LYX_ALIGN_SPECIAL:
1870 case LYX_ALIGN_LEFT: {
1872 if (getParLanguage(bparams)->babel() != "hebrew")
1873 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1875 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1876 os << from_ascii(output);
1877 adjust_row_column(output, texrow, column);
1879 } case LYX_ALIGN_RIGHT: {
1881 if (getParLanguage(bparams)->babel() != "hebrew")
1882 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1884 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1885 os << from_ascii(output);
1886 adjust_row_column(output, texrow, column);
1888 } case LYX_ALIGN_CENTER: {
1890 output = corrected_env("\n\\par\\end", "center", ownerCode());
1891 os << from_ascii(output);
1892 adjust_row_column(output, texrow, column);
1901 // This one spits out the text of the paragraph
1902 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1903 BufferParams const & bparams,
1904 LyXFont const & outerfont,
1905 odocstream & os, TexRow & texrow,
1906 OutputParams const & runparams) const
1908 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1910 bool return_value = false;
1912 LyXLayout_ptr style;
1914 // well we have to check if we are in an inset with unlimited
1915 // length (all in one row) if that is true then we don't allow
1916 // any special options in the paragraph and also we don't allow
1917 // any environment other than the default layout of the text class
1919 bool asdefault = forceDefaultParagraphs();
1922 style = bparams.getLyXTextClass().defaultLayout();
1927 // Current base font for all inherited font changes, without any
1928 // change caused by an individual character, except for the language:
1929 // It is set to the language of the first character.
1930 // As long as we are in the label, this font is the base font of the
1931 // label. Before the first body character it is set to the base font
1935 // output change tracking marks only if desired,
1936 // if dvipost is installed,
1937 // and with dvi/ps (other formats don't work)
1938 bool const output = bparams.outputChanges
1939 && runparams.flavor == OutputParams::LATEX
1940 && LaTeXFeatures::isAvailable("dvipost");
1942 // Maybe we have to create a optional argument.
1943 pos_type body_pos = beginOfBody();
1944 unsigned int column = 0;
1947 // the optional argument is kept in curly brackets in
1948 // case it contains a ']'
1951 basefont = getLabelFont(bparams, outerfont);
1953 basefont = getLayoutFont(bparams, outerfont);
1956 // Which font is currently active?
1957 LyXFont running_font(basefont);
1958 // Do we have an open font change?
1959 bool open_font = false;
1961 Change::Type runningChangeType = Change::UNCHANGED;
1963 texrow.start(id(), 0);
1965 // if the paragraph is empty, the loop will not be entered at all
1967 if (style->isCommand()) {
1972 column += startTeXParParams(bparams, os, texrow,
1973 runparams.moving_arg);
1976 for (pos_type i = 0; i < size(); ++i) {
1977 // First char in paragraph or after label?
1978 if (i == body_pos) {
1981 column += running_font.latexWriteEndChanges(
1982 os, basefont, basefont);
1985 basefont = getLayoutFont(bparams, outerfont);
1986 running_font = basefont;
1988 column += Changes::latexMarkChange(os,
1989 runningChangeType, Change::UNCHANGED, output);
1990 runningChangeType = Change::UNCHANGED;
1995 if (style->isCommand()) {
2001 column += startTeXParParams(bparams, os,
2003 runparams.moving_arg);
2006 Change::Type changeType = pimpl_->lookupChange(i).type;
2008 // do not output text which is marked deleted
2009 // if change tracking output is disabled
2010 if (!output && changeType == Change::DELETED) {
2011 runningChangeType = changeType;
2017 column += Changes::latexMarkChange(os, runningChangeType,
2018 changeType, output);
2019 runningChangeType = changeType;
2021 value_type const c = getChar(i);
2023 // Fully instantiated font
2024 LyXFont const font = getFont(bparams, i, outerfont);
2026 LyXFont const last_font = running_font;
2028 // Do we need to close the previous font?
2030 (font != running_font ||
2031 font.language() != running_font.language()))
2033 column += running_font.latexWriteEndChanges(
2035 (i == body_pos-1) ? basefont : font);
2036 running_font = basefont;
2040 // Switch file encoding if necessary
2041 int const count = switchEncoding(os, bparams,
2042 runparams.moving_arg, *(runparams.encoding),
2043 *(font.language()->encoding()));
2046 runparams.encoding = font.language()->encoding();
2049 // Do we need to change font?
2050 if ((font != running_font ||
2051 font.language() != running_font.language()) &&
2054 column += font.latexWriteStartChanges(os, basefont,
2056 running_font = font;
2061 // Do not print the separation of the optional argument
2062 // if style->pass_thru is false. This works because
2063 // simpleTeXSpecialChars ignores spaces if
2064 // style->pass_thru is false.
2065 if (i != body_pos - 1) {
2066 if (pimpl_->simpleTeXBlanks(
2067 *(runparams.encoding), os, texrow,
2068 i, column, font, *style))
2069 // A surrogate pair was output. We
2070 // must not call simpleTeXSpecialChars
2071 // in this iteration, since
2072 // simpleTeXBlanks incremented i, and
2073 // simpleTeXSpecialChars would output
2074 // the combining character again.
2079 OutputParams rp = runparams;
2080 rp.free_spacing = style->free_spacing;
2081 rp.local_font = &font;
2082 rp.intitle = style->intitle;
2083 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2084 texrow, rp, running_font,
2085 basefont, outerfont, open_font,
2086 runningChangeType, *style, i, column, c);
2089 // If we have an open font definition, we have to close it
2091 #ifdef FIXED_LANGUAGE_END_DETECTION
2094 .latexWriteEndChanges(os, basefont,
2095 next_->getFont(bparams, 0, outerfont));
2097 running_font.latexWriteEndChanges(os, basefont,
2101 #ifdef WITH_WARNINGS
2102 //#warning For now we ALWAYS have to close the foreign font settings if they are
2103 //#warning there as we start another \selectlanguage with the next paragraph if
2104 //#warning we are in need of this. This should be fixed sometime (Jug)
2106 running_font.latexWriteEndChanges(os, basefont, basefont);
2110 column += Changes::latexMarkChange(os,
2111 runningChangeType, Change::UNCHANGED, output);
2113 // Needed if there is an optional argument but no contents.
2114 if (body_pos > 0 && body_pos == size()) {
2116 return_value = false;
2120 column += endTeXParParams(bparams, os, texrow,
2121 runparams.moving_arg);
2124 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2125 return return_value;
2142 string tag_name(PAR_TAG const & pt) {
2144 case PAR_NONE: return "!-- --";
2145 case TT: return "tt";
2146 case SF: return "sf";
2147 case BF: return "bf";
2148 case IT: return "it";
2149 case SL: return "sl";
2150 case EM: return "em";
2157 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2159 p1 = static_cast<PAR_TAG>(p1 | p2);
2164 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2166 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2172 bool Paragraph::emptyTag() const
2174 for (pos_type i = 0; i < size(); ++i) {
2176 InsetBase const * inset = getInset(i);
2177 InsetBase::Code lyx_code = inset->lyxCode();
2178 if (lyx_code != InsetBase::TOC_CODE &&
2179 lyx_code != InsetBase::INCLUDE_CODE &&
2180 lyx_code != InsetBase::GRAPHICS_CODE &&
2181 lyx_code != InsetBase::ERT_CODE &&
2182 lyx_code != InsetBase::FLOAT_CODE &&
2183 lyx_code != InsetBase::TABULAR_CODE) {
2187 value_type c = getChar(i);
2188 if (c != ' ' && c != '\t')
2196 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2198 for (pos_type i = 0; i < size(); ++i) {
2200 InsetBase const * inset = getInset(i);
2201 InsetBase::Code lyx_code = inset->lyxCode();
2202 if (lyx_code == InsetBase::LABEL_CODE) {
2203 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2204 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2213 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2216 for (i = 0; i < size(); ++i) {
2218 InsetBase const * inset = getInset(i);
2219 inset->docbook(buf, os, runparams);
2221 value_type c = getChar(i);
2224 os << sgml::escapeChar(c);
2231 bool Paragraph::onlyText(Buffer const & buf, LyXFont const & outerfont, pos_type initial) const
2235 for (pos_type i = initial; i < size(); ++i) {
2236 LyXFont font = getFont(buf.params(), i, outerfont);
2239 if (i != initial && font != font_old)
2248 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2250 OutputParams const & runparams,
2251 LyXFont const & outerfont,
2252 pos_type initial) const
2254 bool emph_flag = false;
2256 LyXLayout_ptr const & style = layout();
2258 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2260 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2263 // parsing main loop
2264 for (pos_type i = initial; i < size(); ++i) {
2265 LyXFont font = getFont(buf.params(), i, outerfont);
2267 // handle <emphasis> tag
2268 if (font_old.emph() != font.emph()) {
2269 if (font.emph() == LyXFont::ON) {
2272 } else if (i != initial) {
2273 os << "</emphasis>";
2279 InsetBase const * inset = getInset(i);
2280 inset->docbook(buf, os, runparams);
2282 value_type c = getChar(i);
2284 if (style->pass_thru)
2287 os << sgml::escapeChar(c);
2293 os << "</emphasis>";
2296 if (style->free_spacing)
2298 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2303 bool Paragraph::isNewline(pos_type pos) const
2306 && getInset(pos)->lyxCode() == InsetBase::NEWLINE_CODE;
2310 bool Paragraph::isLineSeparator(pos_type pos) const
2312 value_type const c = getChar(pos);
2313 return isLineSeparatorChar(c)
2314 || (c == Paragraph::META_INSET && getInset(pos) &&
2315 getInset(pos)->isLineSeparator());
2319 /// Used by the spellchecker
2320 bool Paragraph::isLetter(pos_type pos) const
2323 return getInset(pos)->isLetter();
2325 value_type const c = getChar(pos);
2326 return isLetterChar(c) || isDigit(c);
2332 Paragraph::getParLanguage(BufferParams const & bparams) const
2335 return getFirstFontSettings(bparams).language();
2336 #ifdef WITH_WARNINGS
2337 #warning FIXME we should check the prev par as well (Lgb)
2339 return bparams.language;
2343 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2345 return lyxrc.rtl_support
2346 && getParLanguage(bparams)->rightToLeft()
2347 && ownerCode() != InsetBase::ERT_CODE;
2351 void Paragraph::changeLanguage(BufferParams const & bparams,
2352 Language const * from, Language const * to)
2354 // change language including dummy font change at the end
2355 for (pos_type i = 0; i <= size(); ++i) {
2356 LyXFont font = getFontSettings(bparams, i);
2357 if (font.language() == from) {
2358 font.setLanguage(to);
2365 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2367 Language const * doc_language = bparams.language;
2368 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2369 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2371 for (; cit != end; ++cit)
2372 if (cit->font().language() != ignore_language &&
2373 cit->font().language() != latex_language &&
2374 cit->font().language() != doc_language)
2380 // Convert the paragraph to a string.
2381 // Used for building the table of contents
2382 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2384 return asString(buffer, 0, size(), label);
2388 docstring const Paragraph::asString(Buffer const & buffer,
2389 pos_type beg, pos_type end, bool label) const
2392 odocstringstream os;
2394 if (beg == 0 && label && !params().labelString().empty())
2395 os << params().labelString() << ' ';
2397 for (pos_type i = beg; i < end; ++i) {
2398 value_type const c = getUChar(buffer.params(), i);
2401 else if (c == META_INSET)
2402 getInset(i)->textString(buffer, os);
2409 void Paragraph::setInsetOwner(InsetBase * inset)
2411 pimpl_->inset_owner = inset;
2415 Change const & Paragraph::lookupChange(pos_type pos) const
2417 BOOST_ASSERT(pos <= size());
2418 return pimpl_->lookupChange(pos);
2422 bool Paragraph::isChanged(pos_type start, pos_type end) const
2424 return pimpl_->isChanged(start, end);
2428 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2430 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2434 void Paragraph::setChange(Change const & change)
2436 pimpl_->setChange(change);
2440 void Paragraph::setChange(pos_type pos, Change const & change)
2442 pimpl_->setChange(pos, change);
2446 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2448 return pimpl_->acceptChanges(bparams, start, end);
2452 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2454 return pimpl_->rejectChanges(bparams, start, end);
2458 int Paragraph::id() const
2464 LyXLayout_ptr const & Paragraph::layout() const
2470 void Paragraph::layout(LyXLayout_ptr const & new_layout)
2472 layout_ = new_layout;
2476 InsetBase * Paragraph::inInset() const
2478 return pimpl_->inset_owner;
2482 InsetBase::Code Paragraph::ownerCode() const
2484 return pimpl_->inset_owner
2485 ? pimpl_->inset_owner->lyxCode() : InsetBase::NO_CODE;
2489 ParagraphParameters & Paragraph::params()
2491 return pimpl_->params;
2495 ParagraphParameters const & Paragraph::params() const
2497 return pimpl_->params;
2501 bool Paragraph::isFreeSpacing() const
2503 if (layout()->free_spacing)
2506 // for now we just need this, later should we need this in some
2507 // other way we can always add a function to InsetBase too.
2508 return ownerCode() == InsetBase::ERT_CODE;
2512 bool Paragraph::allowEmpty() const
2514 if (layout()->keepempty)
2516 return ownerCode() == InsetBase::ERT_CODE;
2520 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2522 if (!Encodings::is_arabic(c))
2525 value_type const prev_char = pos > 0 ? getChar(pos - 1) : ' ';
2526 value_type next_char = ' ';
2528 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2529 value_type const par_char = getChar(i);
2530 if (!Encodings::isComposeChar_arabic(par_char)) {
2531 next_char = par_char;
2536 if (Encodings::is_arabic(next_char)) {
2537 if (Encodings::is_arabic(prev_char) &&
2538 !Encodings::is_arabic_special(prev_char))
2539 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2541 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2543 if (Encodings::is_arabic(prev_char) &&
2544 !Encodings::is_arabic_special(prev_char))
2545 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2547 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2552 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2557 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2559 // expand at the end of a row only if there is another hfill on the same row
2560 if (pos == row.endpos() - 1) {
2561 for (pos_type i = row.pos(); i < pos; i++) {
2568 // expand at the beginning of a row only if it is the first row of a paragraph
2569 if (pos == row.pos()) {
2573 // do not expand in some labels
2574 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2577 // if there is anything between the first char of the row and
2578 // the specified position that is neither a newline nor an hfill,
2579 // the hfill will be expanded, otherwise it won't
2580 for (pos_type i = row.pos(); i < pos; i++) {
2581 if (!isNewline(i) && !isHfill(i))
2588 bool Paragraph::checkBiblio(bool track_changes)
2590 // Add bibitem insets if necessary
2591 if (layout()->labeltype != LABEL_BIBLIO)
2594 bool hasbibitem = !insetlist.empty()
2595 // Insist on it being in pos 0
2596 && getChar(0) == Paragraph::META_INSET
2597 && insetlist.begin()->inset->lyxCode() == InsetBase::BIBITEM_CODE;
2602 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2603 insertInset(0, static_cast<InsetBase *>(inset),
2604 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));