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/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, Inset * 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);
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, Font const & f)
139 pos_type pos() const { return pos_; }
141 void pos(pos_type p) { pos_ = p; }
143 Font const & font() const { return font_; }
145 void font(Font 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 Font::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 Font::IGNORE_* and Font::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,
188 Layout const & style);
190 void simpleTeXSpecialChars(Buffer const &, BufferParams const &,
192 TexRow & texrow, OutputParams const &,
195 Font const & outerfont,
197 Change::Type & running_change,
198 Layout const & style,
200 unsigned int & column, value_type const c);
203 void validate(LaTeXFeatures & features,
204 Layout 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, Font());
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, Inset * 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, Font());
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,
587 Layout 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() == Font::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,
666 Font const & outerfont,
668 Change::Type & running_change,
669 Layout 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 Inset * inset = owner_->getInset(i);
692 // FIXME: remove this check
696 // FIXME: move this to InsetNewline::latex
697 if (inset->lyxCode() == Inset::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() == Font::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() == Inset::GRAPHICS_CODE
744 || inset->lyxCode() == Inset::MATH_CODE
745 || inset->lyxCode() == Inset::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 case '|': case '<': case '>':
793 // In T1 encoding, these characters exist
794 if (lyxrc.fontenc == "T1") {
796 //... but we should avoid ligatures
797 if ((c == '>' || c == '<')
799 && getChar(i + 1) == c) {
800 //os << "\\textcompwordmark{}";
802 // Jean-Marc, have a look at
803 // this. I think this works
811 // Typewriter font also has them
812 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
816 // Otherwise, we use what LaTeX
820 os << "\\textless{}";
824 os << "\\textgreater{}";
834 case '-': // "--" in Typewriter mode -> "-{}-"
835 if (i <= size() - 2 &&
836 getChar(i + 1) == '-' &&
837 running_font.family() == Font::TYPEWRITER_FAMILY) {
846 os << "\\char`\\\"{}";
851 case '%': case '#': case '{':
859 os << "\\textasciitilde{}";
864 os << "\\textasciicircum{}";
869 // avoid being mistaken for optional arguments
877 // Blanks are printed before font switching.
878 // Sure? I am not! (try nice-latex)
879 // I am sure it's correct. LyX might be smarter
880 // in the future, but for now, nothing wrong is
886 // I assume this is hack treating typewriter as verbatim
887 // FIXME UNICODE: This can fail if c cannot be encoded
888 // in the current encoding.
889 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
898 // FIXME: if we have "LaTeX" with a font
899 // change in the middle (before the 'T', then
900 // the "TeX" part is still special cased.
901 // Really we should only operate this on
902 // "words" for some definition of word
906 for (; pnr < phrases_nr; ++pnr) {
907 if (isTextAt(special_phrases[pnr].phrase, i)) {
908 os << special_phrases[pnr].macro;
909 i += special_phrases[pnr].phrase.length() - 1;
910 column += special_phrases[pnr].macro.length() - 1;
915 if (pnr == phrases_nr && c != '\0') {
916 Encoding const & encoding = *(runparams.encoding);
917 if (i < size() - 1) {
918 char_type next = getChar(i + 1);
919 if (Encodings::isCombiningChar(next)) {
920 column += latexSurrogatePair(os, c, next, encoding) - 1;
925 docstring const latex = encoding.latexChar(c);
926 if (latex.length() > 1 &&
927 latex[latex.length() - 1] != '}') {
928 // Prevent eating of a following
929 // space or command corruption by
930 // following characters
931 column += latex.length() + 1;
934 column += latex.length() - 1;
944 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
945 Layout const & layout) const
947 BufferParams const & bparams = features.bufferParams();
950 if (!params.spacing().isDefault())
951 features.require("setspace");
954 features.useLayout(layout.name());
957 Language const * doc_language = bparams.language;
959 FontList::const_iterator fcit = fontlist.begin();
960 FontList::const_iterator fend = fontlist.end();
961 for (; fcit != fend; ++fcit) {
962 if (fcit->font().noun() == Font::ON) {
963 LYXERR(Debug::LATEX) << "font.noun: "
964 << fcit->font().noun()
966 features.require("noun");
967 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
968 << to_utf8(fcit->font().stateText(0))
971 switch (fcit->font().color()) {
975 // probably we should put here all interface colors used for
976 // font displaying! For now I just add this ones I know of (Jug)
981 features.require("color");
982 LYXERR(Debug::LATEX) << "Color enabled. Font: "
983 << to_utf8(fcit->font().stateText(0))
987 Language const * language = fcit->font().language();
988 if (language->babel() != doc_language->babel() &&
989 language != ignore_language &&
990 language != latex_language)
992 features.useLanguage(language);
993 LYXERR(Debug::LATEX) << "Found language "
994 << language->babel() << endl;
998 if (!params.leftIndent().zero())
999 features.require("ParagraphLeftIndent");
1002 InsetList::const_iterator icit = owner_->insetlist.begin();
1003 InsetList::const_iterator iend = owner_->insetlist.end();
1004 for (; icit != iend; ++icit) {
1006 icit->inset->validate(features);
1007 if (layout.needprotect &&
1008 icit->inset->lyxCode() == Inset::FOOT_CODE)
1009 features.require("NeedLyXFootnoteCode");
1013 // then the contents
1014 for (pos_type i = 0; i < size() ; ++i) {
1015 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1016 if (!special_phrases[pnr].builtin
1017 && isTextAt(special_phrases[pnr].phrase, i)) {
1018 features.require(special_phrases[pnr].phrase);
1022 Encodings::validate(getChar(i), features);
1030 /////////////////////////////////////////////////////////////////////
1034 /////////////////////////////////////////////////////////////////////
1038 Paragraph::Paragraph()
1039 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1046 Paragraph::Paragraph(Paragraph const & par)
1047 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1048 layout_(par.layout_),
1049 text_(par.text_), begin_of_body_(par.begin_of_body_),
1050 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1052 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1053 InsetList::iterator it = insetlist.begin();
1054 InsetList::iterator end = insetlist.end();
1055 for (; it != end; ++it)
1056 it->inset = it->inset->clone().release();
1060 Paragraph & Paragraph::operator=(Paragraph const & par)
1062 // needed as we will destroy the pimpl_ before copying it
1064 itemdepth = par.itemdepth;
1066 insetlist = par.insetlist;
1067 InsetList::iterator it = insetlist.begin();
1068 InsetList::iterator end = insetlist.end();
1069 for (; it != end; ++it)
1070 it->inset = it->inset->clone().release();
1072 layout_ = par.layout();
1074 begin_of_body_ = par.begin_of_body_;
1077 pimpl_ = new Pimpl(*par.pimpl_, this);
1083 Paragraph::~Paragraph()
1087 //lyxerr << "Paragraph::paragraph_id = "
1088 // << Paragraph::paragraph_id << endl;
1092 void Paragraph::write(Buffer const & buf, ostream & os,
1093 BufferParams const & bparams,
1094 depth_type & dth) const
1096 // The beginning or end of a deeper (i.e. nested) area?
1097 if (dth != params().depth()) {
1098 if (params().depth() > dth) {
1099 while (params().depth() > dth) {
1100 os << "\n\\begin_deeper";
1104 while (params().depth() < dth) {
1105 os << "\n\\end_deeper";
1111 // First write the layout
1112 os << "\n\\begin_layout " << layout()->name() << '\n';
1116 Font font1(Font::ALL_INHERIT, bparams.language);
1118 Change running_change = Change(Change::UNCHANGED);
1121 for (pos_type i = 0; i <= size(); ++i) {
1123 Change change = pimpl_->lookupChange(i);
1124 Changes::lyxMarkChange(os, column, running_change, change);
1125 running_change = change;
1130 // Write font changes
1131 Font font2 = getFontSettings(bparams, i);
1132 if (font2 != font1) {
1133 font2.lyxWriteChanges(font1, os);
1138 value_type const c = getChar(i);
1142 Inset const * inset = getInset(i);
1144 if (inset->directWrite()) {
1145 // international char, let it write
1146 // code directly so it's shorter in
1148 inset->write(buf, os);
1152 os << "\\begin_inset ";
1153 inset->write(buf, os);
1154 os << "\n\\end_inset\n\n";
1160 os << "\n\\backslash\n";
1164 if (i + 1 < size() && getChar(i + 1) == ' ') {
1171 if ((column > 70 && c == ' ')
1176 // this check is to amend a bug. LyX sometimes
1177 // inserts '\0' this could cause problems.
1179 std::vector<char> tmp = ucs4_to_utf8(c);
1180 tmp.push_back('\0');
1183 lyxerr << "ERROR (Paragraph::writeFile):"
1184 " NULL char in structure." << endl;
1190 os << "\n\\end_layout\n";
1194 void Paragraph::validate(LaTeXFeatures & features) const
1196 pimpl_->validate(features, *layout());
1200 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1202 return pimpl_->eraseChar(pos, trackChanges);
1206 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1208 return pimpl_->eraseChars(start, end, trackChanges);
1212 void Paragraph::insert(pos_type start, docstring const & str,
1213 Font const & font, Change const & change)
1215 for (size_t i = 0, n = str.size(); i != n ; ++i)
1216 insertChar(start + i, str[i], font, change);
1220 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1223 pimpl_->insertChar(pos, c, Change(trackChanges ?
1224 Change::INSERTED : Change::UNCHANGED));
1228 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1229 Font const & font, bool trackChanges)
1231 pimpl_->insertChar(pos, c, Change(trackChanges ?
1232 Change::INSERTED : Change::UNCHANGED));
1237 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1238 Font const & font, Change const & change)
1240 pimpl_->insertChar(pos, c, change);
1245 void Paragraph::insertInset(pos_type pos, Inset * inset,
1246 Change const & change)
1248 pimpl_->insertInset(pos, inset, change);
1252 void Paragraph::insertInset(pos_type pos, Inset * inset,
1253 Font const & font, Change const & change)
1255 pimpl_->insertInset(pos, inset, change);
1260 bool Paragraph::insetAllowed(Inset_code code)
1262 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1266 // Gets uninstantiated font setting at position.
1267 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1271 lyxerr << " pos: " << pos << " size: " << size() << endl;
1272 BOOST_ASSERT(pos <= size());
1275 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1276 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1277 for (; cit != end; ++cit)
1278 if (cit->pos() >= pos)
1284 if (pos == size() && !empty())
1285 return getFontSettings(bparams, pos - 1);
1287 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1291 FontSpan Paragraph::fontSpan(pos_type pos) const
1293 BOOST_ASSERT(pos <= size());
1296 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1297 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1298 for (; cit != end; ++cit) {
1299 if (cit->pos() >= pos) {
1300 if (pos >= beginOfBody())
1301 return FontSpan(std::max(start, beginOfBody()),
1304 return FontSpan(start,
1305 std::min(beginOfBody() - 1,
1308 start = cit->pos() + 1;
1311 // This should not happen, but if so, we take no chances.
1312 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1314 return FontSpan(pos, pos);
1318 // Gets uninstantiated font setting at position 0
1319 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1321 if (!empty() && !pimpl_->fontlist.empty())
1322 return pimpl_->fontlist[0].font();
1324 return Font(Font::ALL_INHERIT, bparams.language);
1328 // Gets the fully instantiated font at a given position in a paragraph
1329 // This is basically the same function as LyXText::GetFont() in text2.cpp.
1330 // The difference is that this one is used for generating the LaTeX file,
1331 // and thus cosmetic "improvements" are disallowed: This has to deliver
1332 // the true picture of the buffer. (Asger)
1333 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1334 Font const & outerfont) const
1336 BOOST_ASSERT(pos >= 0);
1338 Layout_ptr const & lout = layout();
1340 pos_type const body_pos = beginOfBody();
1344 layoutfont = lout->labelfont;
1346 layoutfont = lout->font;
1348 Font font = getFontSettings(bparams, pos);
1349 font.realize(layoutfont);
1350 font.realize(outerfont);
1351 font.realize(bparams.getFont());
1357 Font const Paragraph::getLabelFont
1358 (BufferParams const & bparams, Font const & outerfont) const
1360 Font tmpfont = layout()->labelfont;
1361 tmpfont.setLanguage(getParLanguage(bparams));
1362 tmpfont.realize(outerfont);
1363 tmpfont.realize(bparams.getFont());
1368 Font const Paragraph::getLayoutFont
1369 (BufferParams const & bparams, Font const & outerfont) const
1371 Font tmpfont = layout()->font;
1372 tmpfont.setLanguage(getParLanguage(bparams));
1373 tmpfont.realize(outerfont);
1374 tmpfont.realize(bparams.getFont());
1379 /// Returns the height of the highest font in range
1380 Font_size Paragraph::highestFontInRange
1381 (pos_type startpos, pos_type endpos, Font_size def_size) const
1383 if (pimpl_->fontlist.empty())
1386 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1387 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1388 for (; end_it != end; ++end_it) {
1389 if (end_it->pos() >= endpos)
1396 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1397 for (; cit != end; ++cit) {
1398 if (cit->pos() >= startpos)
1402 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1403 for (; cit != end_it; ++cit) {
1404 Font::FONT_SIZE size = cit->font().size();
1405 if (size == Font::INHERIT_SIZE)
1407 if (size > maxsize && size <= Font::SIZE_HUGER)
1414 Paragraph::value_type
1415 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1417 value_type c = getChar(pos);
1418 if (!lyxrc.rtl_support)
1448 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1455 void Paragraph::setFont(pos_type pos, Font const & font)
1457 BOOST_ASSERT(pos <= size());
1459 // First, reduce font against layout/label font
1460 // Update: The setCharFont() routine in text2.cpp already
1461 // reduces font, so we don't need to do that here. (Asger)
1462 // No need to simplify this because it will disappear
1463 // in a new kernel. (Asger)
1464 // Next search font table
1466 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1467 Pimpl::FontList::iterator it = beg;
1468 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1469 for (; it != endit; ++it) {
1470 if (it->pos() >= pos)
1473 size_t const i = distance(beg, it);
1474 bool notfound = (it == endit);
1476 if (!notfound && pimpl_->fontlist[i].font() == font)
1479 bool begin = pos == 0 || notfound ||
1480 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1481 // Is position pos is a beginning of a font block?
1482 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1483 // Is position pos is the end of a font block?
1484 if (begin && end) { // A single char block
1485 if (i + 1 < pimpl_->fontlist.size() &&
1486 pimpl_->fontlist[i + 1].font() == font) {
1487 // Merge the singleton block with the next block
1488 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1489 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1490 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1491 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1492 // Merge the singleton block with the previous block
1493 pimpl_->fontlist[i - 1].pos(pos);
1494 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1496 pimpl_->fontlist[i].font(font);
1498 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1499 pimpl_->fontlist[i - 1].pos(pos);
1501 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1502 Pimpl::FontTable(pos, font));
1504 pimpl_->fontlist[i].pos(pos - 1);
1505 if (!(i + 1 < pimpl_->fontlist.size() &&
1506 pimpl_->fontlist[i + 1].font() == font))
1507 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1508 Pimpl::FontTable(pos, font));
1509 } else { // The general case. The block is splitted into 3 blocks
1510 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1511 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1512 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1513 Pimpl::FontTable(pos, font));
1518 void Paragraph::makeSameLayout(Paragraph const & par)
1520 layout(par.layout());
1522 params() = par.params();
1526 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1528 if (isFreeSpacing())
1534 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1535 if (eraseChar(pos, trackChanges))
1541 return count > 0 || pos > 0;
1545 bool Paragraph::hasSameLayout(Paragraph const & par) const
1547 return par.layout() == layout() && params().sameLayout(par.params());
1551 depth_type Paragraph::getDepth() const
1553 return params().depth();
1557 depth_type Paragraph::getMaxDepthAfter() const
1559 if (layout()->isEnvironment())
1560 return params().depth() + 1;
1562 return params().depth();
1566 char Paragraph::getAlign() const
1568 if (params().align() == LYX_ALIGN_LAYOUT)
1569 return layout()->align;
1571 return params().align();
1575 docstring const & Paragraph::getLabelstring() const
1577 return params().labelString();
1581 // the next two functions are for the manual labels
1582 docstring const Paragraph::getLabelWidthString() const
1584 if (!params().labelWidthString().empty())
1585 return params().labelWidthString();
1587 return _("Senseless with this layout!");
1591 void Paragraph::setLabelWidthString(docstring const & s)
1593 params().labelWidthString(s);
1597 docstring const Paragraph::translateIfPossible(docstring const & s,
1598 BufferParams const & bparams) const
1600 if (!support::isAscii(s) || s.empty()) {
1601 // This must be a user defined layout. We cannot translate
1602 // this, since gettext accepts only ascii keys.
1605 // Probably standard layout, try to translate
1606 Messages & m = getMessages(getParLanguage(bparams)->code());
1607 return m.get(to_ascii(s));
1611 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1612 BufferParams const & bparams, bool process_appendix) const
1614 TextClass const & tclass = bparams.getTextClass();
1617 if (process_appendix && params().appendix())
1618 fmt = translateIfPossible(layout->labelstring_appendix(),
1621 fmt = translateIfPossible(layout->labelstring(), bparams);
1623 // handle 'inherited level parts' in 'fmt',
1624 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1625 size_t const i = fmt.find('@', 0);
1626 if (i != docstring::npos) {
1627 size_t const j = fmt.find('@', i + 1);
1628 if (j != docstring::npos) {
1629 docstring parent(fmt, i + 1, j - i - 1);
1631 docstring label = expandLabel(tclass[to_utf8(parent)], bparams);
1632 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1636 return tclass.counters().counterLabel(fmt);
1640 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1643 params().labelWidthString(docstring());
1644 params().align(LYX_ALIGN_LAYOUT);
1645 params().spacing(Spacing(Spacing::Default));
1649 pos_type Paragraph::beginOfBody() const
1651 return begin_of_body_;
1655 void Paragraph::setBeginOfBody()
1657 if (layout()->labeltype != LABEL_MANUAL) {
1662 // Unroll the first two cycles of the loop
1663 // and remember the previous character to
1664 // remove unnecessary getChar() calls
1666 pos_type end = size();
1667 if (i < end && !isNewline(i)) {
1669 char_type previous_char = 0;
1672 previous_char = text_[i];
1673 if (!isNewline(i)) {
1675 while (i < end && previous_char != ' ') {
1680 previous_char = temp;
1690 // returns -1 if inset not found
1691 int Paragraph::getPositionOfInset(Inset const * inset) const
1694 InsetList::const_iterator it = insetlist.begin();
1695 InsetList::const_iterator end = insetlist.end();
1696 for (; it != end; ++it)
1697 if (it->inset == inset)
1703 InsetBibitem * Paragraph::bibitem() const
1705 if (!insetlist.empty()) {
1706 Inset * inset = insetlist.begin()->inset;
1707 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1708 return static_cast<InsetBibitem *>(inset);
1714 bool Paragraph::forceDefaultParagraphs() const
1716 return inInset() && inInset()->forceDefaultParagraphs(0);
1722 // paragraphs inside floats need different alignment tags to avoid
1725 bool noTrivlistCentering(Inset::Code code)
1727 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1731 string correction(string const & orig)
1733 if (orig == "flushleft")
1734 return "raggedright";
1735 if (orig == "flushright")
1736 return "raggedleft";
1737 if (orig == "center")
1743 string const corrected_env(string const & suffix, string const & env,
1746 string output = suffix + "{";
1747 if (noTrivlistCentering(code))
1748 output += correction(env);
1752 if (suffix == "\\begin")
1758 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1760 if (!contains(str, "\n"))
1761 column += str.size();
1765 column = rsplit(str, tmp, '\n').size();
1772 // This could go to ParagraphParameters if we want to
1773 int Paragraph::startTeXParParams(BufferParams const & bparams,
1774 odocstream & os, TexRow & texrow,
1775 bool moving_arg) const
1779 if (params().noindent()) {
1780 os << "\\noindent ";
1784 switch (params().align()) {
1785 case LYX_ALIGN_NONE:
1786 case LYX_ALIGN_BLOCK:
1787 case LYX_ALIGN_LAYOUT:
1788 case LYX_ALIGN_SPECIAL:
1790 case LYX_ALIGN_LEFT:
1791 case LYX_ALIGN_RIGHT:
1792 case LYX_ALIGN_CENTER:
1800 switch (params().align()) {
1801 case LYX_ALIGN_NONE:
1802 case LYX_ALIGN_BLOCK:
1803 case LYX_ALIGN_LAYOUT:
1804 case LYX_ALIGN_SPECIAL:
1806 case LYX_ALIGN_LEFT: {
1808 if (getParLanguage(bparams)->babel() != "hebrew")
1809 output = corrected_env("\\begin", "flushleft", ownerCode());
1811 output = corrected_env("\\begin", "flushright", ownerCode());
1812 os << from_ascii(output);
1813 adjust_row_column(output, texrow, column);
1815 } case LYX_ALIGN_RIGHT: {
1817 if (getParLanguage(bparams)->babel() != "hebrew")
1818 output = corrected_env("\\begin", "flushright", ownerCode());
1820 output = corrected_env("\\begin", "flushleft", ownerCode());
1821 os << from_ascii(output);
1822 adjust_row_column(output, texrow, column);
1824 } case LYX_ALIGN_CENTER: {
1826 output = corrected_env("\\begin", "center", ownerCode());
1827 os << from_ascii(output);
1828 adjust_row_column(output, texrow, column);
1837 // This could go to ParagraphParameters if we want to
1838 int Paragraph::endTeXParParams(BufferParams const & bparams,
1839 odocstream & os, TexRow & texrow,
1840 bool moving_arg) const
1844 switch (params().align()) {
1845 case LYX_ALIGN_NONE:
1846 case LYX_ALIGN_BLOCK:
1847 case LYX_ALIGN_LAYOUT:
1848 case LYX_ALIGN_SPECIAL:
1850 case LYX_ALIGN_LEFT:
1851 case LYX_ALIGN_RIGHT:
1852 case LYX_ALIGN_CENTER:
1860 switch (params().align()) {
1861 case LYX_ALIGN_NONE:
1862 case LYX_ALIGN_BLOCK:
1863 case LYX_ALIGN_LAYOUT:
1864 case LYX_ALIGN_SPECIAL:
1866 case LYX_ALIGN_LEFT: {
1868 if (getParLanguage(bparams)->babel() != "hebrew")
1869 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1871 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1872 os << from_ascii(output);
1873 adjust_row_column(output, texrow, column);
1875 } case LYX_ALIGN_RIGHT: {
1877 if (getParLanguage(bparams)->babel() != "hebrew")
1878 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1880 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1881 os << from_ascii(output);
1882 adjust_row_column(output, texrow, column);
1884 } case LYX_ALIGN_CENTER: {
1886 output = corrected_env("\n\\par\\end", "center", ownerCode());
1887 os << from_ascii(output);
1888 adjust_row_column(output, texrow, column);
1897 // This one spits out the text of the paragraph
1898 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1899 BufferParams const & bparams,
1900 Font const & outerfont,
1901 odocstream & os, TexRow & texrow,
1902 OutputParams const & runparams) const
1904 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1906 bool return_value = false;
1910 // well we have to check if we are in an inset with unlimited
1911 // length (all in one row) if that is true then we don't allow
1912 // any special options in the paragraph and also we don't allow
1913 // any environment other than the default layout of the text class
1915 bool asdefault = forceDefaultParagraphs();
1918 style = bparams.getTextClass().defaultLayout();
1923 // Current base font for all inherited font changes, without any
1924 // change caused by an individual character, except for the language:
1925 // It is set to the language of the first character.
1926 // As long as we are in the label, this font is the base font of the
1927 // label. Before the first body character it is set to the base font
1931 // output change tracking marks only if desired,
1932 // if dvipost is installed,
1933 // and with dvi/ps (other formats don't work)
1934 bool const output = bparams.outputChanges
1935 && runparams.flavor == OutputParams::LATEX
1936 && LaTeXFeatures::isAvailable("dvipost");
1938 // Maybe we have to create a optional argument.
1939 pos_type body_pos = beginOfBody();
1940 unsigned int column = 0;
1943 // the optional argument is kept in curly brackets in
1944 // case it contains a ']'
1947 basefont = getLabelFont(bparams, outerfont);
1949 basefont = getLayoutFont(bparams, outerfont);
1952 // Which font is currently active?
1953 Font running_font(basefont);
1954 // Do we have an open font change?
1955 bool open_font = false;
1957 Change::Type runningChangeType = Change::UNCHANGED;
1959 texrow.start(id(), 0);
1961 // if the paragraph is empty, the loop will not be entered at all
1963 if (style->isCommand()) {
1968 column += startTeXParParams(bparams, os, texrow,
1969 runparams.moving_arg);
1972 for (pos_type i = 0; i < size(); ++i) {
1973 // First char in paragraph or after label?
1974 if (i == body_pos) {
1977 column += running_font.latexWriteEndChanges(
1978 os, basefont, basefont);
1981 basefont = getLayoutFont(bparams, outerfont);
1982 running_font = basefont;
1984 column += Changes::latexMarkChange(os,
1985 runningChangeType, Change::UNCHANGED, output);
1986 runningChangeType = Change::UNCHANGED;
1991 if (style->isCommand()) {
1997 column += startTeXParParams(bparams, os,
1999 runparams.moving_arg);
2002 Change::Type changeType = pimpl_->lookupChange(i).type;
2004 // do not output text which is marked deleted
2005 // if change tracking output is disabled
2006 if (!output && changeType == Change::DELETED) {
2007 runningChangeType = changeType;
2013 column += Changes::latexMarkChange(os, runningChangeType,
2014 changeType, output);
2015 runningChangeType = changeType;
2017 value_type const c = getChar(i);
2019 // Fully instantiated font
2020 Font const font = getFont(bparams, i, outerfont);
2022 Font const last_font = running_font;
2024 // Do we need to close the previous font?
2026 (font != running_font ||
2027 font.language() != running_font.language()))
2029 column += running_font.latexWriteEndChanges(
2031 (i == body_pos-1) ? basefont : font);
2032 running_font = basefont;
2036 // Switch file encoding if necessary
2037 int const count = switchEncoding(os, bparams,
2038 runparams.moving_arg, *(runparams.encoding),
2039 *(font.language()->encoding()));
2042 runparams.encoding = font.language()->encoding();
2045 // Do we need to change font?
2046 if ((font != running_font ||
2047 font.language() != running_font.language()) &&
2050 column += font.latexWriteStartChanges(os, basefont,
2052 running_font = font;
2057 // Do not print the separation of the optional argument
2058 // if style->pass_thru is false. This works because
2059 // simpleTeXSpecialChars ignores spaces if
2060 // style->pass_thru is false.
2061 if (i != body_pos - 1) {
2062 if (pimpl_->simpleTeXBlanks(
2063 *(runparams.encoding), os, texrow,
2064 i, column, font, *style))
2065 // A surrogate pair was output. We
2066 // must not call simpleTeXSpecialChars
2067 // in this iteration, since
2068 // simpleTeXBlanks incremented i, and
2069 // simpleTeXSpecialChars would output
2070 // the combining character again.
2075 OutputParams rp = runparams;
2076 rp.free_spacing = style->free_spacing;
2077 rp.local_font = &font;
2078 rp.intitle = style->intitle;
2079 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2080 texrow, rp, running_font,
2081 basefont, outerfont, open_font,
2082 runningChangeType, *style, i, column, c);
2085 // If we have an open font definition, we have to close it
2087 #ifdef FIXED_LANGUAGE_END_DETECTION
2090 .latexWriteEndChanges(os, basefont,
2091 next_->getFont(bparams, 0, outerfont));
2093 running_font.latexWriteEndChanges(os, basefont,
2097 #ifdef WITH_WARNINGS
2098 //#warning For now we ALWAYS have to close the foreign font settings if they are
2099 //#warning there as we start another \selectlanguage with the next paragraph if
2100 //#warning we are in need of this. This should be fixed sometime (Jug)
2102 running_font.latexWriteEndChanges(os, basefont, basefont);
2106 column += Changes::latexMarkChange(os,
2107 runningChangeType, Change::UNCHANGED, output);
2109 // Needed if there is an optional argument but no contents.
2110 if (body_pos > 0 && body_pos == size()) {
2112 return_value = false;
2116 column += endTeXParParams(bparams, os, texrow,
2117 runparams.moving_arg);
2120 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2121 return return_value;
2138 string tag_name(PAR_TAG const & pt) {
2140 case PAR_NONE: return "!-- --";
2141 case TT: return "tt";
2142 case SF: return "sf";
2143 case BF: return "bf";
2144 case IT: return "it";
2145 case SL: return "sl";
2146 case EM: return "em";
2153 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2155 p1 = static_cast<PAR_TAG>(p1 | p2);
2160 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2162 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2168 bool Paragraph::emptyTag() const
2170 for (pos_type i = 0; i < size(); ++i) {
2172 Inset const * inset = getInset(i);
2173 Inset::Code lyx_code = inset->lyxCode();
2174 if (lyx_code != Inset::TOC_CODE &&
2175 lyx_code != Inset::INCLUDE_CODE &&
2176 lyx_code != Inset::GRAPHICS_CODE &&
2177 lyx_code != Inset::ERT_CODE &&
2178 lyx_code != Inset::FLOAT_CODE &&
2179 lyx_code != Inset::TABULAR_CODE) {
2183 value_type c = getChar(i);
2184 if (c != ' ' && c != '\t')
2192 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2194 for (pos_type i = 0; i < size(); ++i) {
2196 Inset const * inset = getInset(i);
2197 Inset::Code lyx_code = inset->lyxCode();
2198 if (lyx_code == Inset::LABEL_CODE) {
2199 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2200 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2209 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2212 for (i = 0; i < size(); ++i) {
2214 Inset const * inset = getInset(i);
2215 inset->docbook(buf, os, runparams);
2217 value_type c = getChar(i);
2220 os << sgml::escapeChar(c);
2227 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2231 for (pos_type i = initial; i < size(); ++i) {
2232 Font font = getFont(buf.params(), i, outerfont);
2235 if (i != initial && font != font_old)
2244 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2246 OutputParams const & runparams,
2247 Font const & outerfont,
2248 pos_type initial) const
2250 bool emph_flag = false;
2252 Layout_ptr const & style = layout();
2254 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2256 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2259 // parsing main loop
2260 for (pos_type i = initial; i < size(); ++i) {
2261 Font font = getFont(buf.params(), i, outerfont);
2263 // handle <emphasis> tag
2264 if (font_old.emph() != font.emph()) {
2265 if (font.emph() == Font::ON) {
2268 } else if (i != initial) {
2269 os << "</emphasis>";
2275 Inset const * inset = getInset(i);
2276 inset->docbook(buf, os, runparams);
2278 value_type c = getChar(i);
2280 if (style->pass_thru)
2283 os << sgml::escapeChar(c);
2289 os << "</emphasis>";
2292 if (style->free_spacing)
2294 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2299 bool Paragraph::isNewline(pos_type pos) const
2302 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2306 bool Paragraph::isLineSeparator(pos_type pos) const
2308 value_type const c = getChar(pos);
2309 return isLineSeparatorChar(c)
2310 || (c == Paragraph::META_INSET && getInset(pos) &&
2311 getInset(pos)->isLineSeparator());
2315 /// Used by the spellchecker
2316 bool Paragraph::isLetter(pos_type pos) const
2319 return getInset(pos)->isLetter();
2321 value_type const c = getChar(pos);
2322 return isLetterChar(c) || isDigit(c);
2328 Paragraph::getParLanguage(BufferParams const & bparams) const
2331 return getFirstFontSettings(bparams).language();
2332 #ifdef WITH_WARNINGS
2333 #warning FIXME we should check the prev par as well (Lgb)
2335 return bparams.language;
2339 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2341 return lyxrc.rtl_support
2342 && getParLanguage(bparams)->rightToLeft()
2343 && ownerCode() != Inset::ERT_CODE;
2347 void Paragraph::changeLanguage(BufferParams const & bparams,
2348 Language const * from, Language const * to)
2350 // change language including dummy font change at the end
2351 for (pos_type i = 0; i <= size(); ++i) {
2352 Font font = getFontSettings(bparams, i);
2353 if (font.language() == from) {
2354 font.setLanguage(to);
2361 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2363 Language const * doc_language = bparams.language;
2364 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2365 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2367 for (; cit != end; ++cit)
2368 if (cit->font().language() != ignore_language &&
2369 cit->font().language() != latex_language &&
2370 cit->font().language() != doc_language)
2376 // Convert the paragraph to a string.
2377 // Used for building the table of contents
2378 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2380 return asString(buffer, 0, size(), label);
2384 docstring const Paragraph::asString(Buffer const & buffer,
2385 pos_type beg, pos_type end, bool label) const
2388 odocstringstream os;
2390 if (beg == 0 && label && !params().labelString().empty())
2391 os << params().labelString() << ' ';
2393 for (pos_type i = beg; i < end; ++i) {
2394 value_type const c = getUChar(buffer.params(), i);
2397 else if (c == META_INSET)
2398 getInset(i)->textString(buffer, os);
2405 void Paragraph::setInsetOwner(Inset * inset)
2407 pimpl_->inset_owner = inset;
2411 Change const & Paragraph::lookupChange(pos_type pos) const
2413 BOOST_ASSERT(pos <= size());
2414 return pimpl_->lookupChange(pos);
2418 bool Paragraph::isChanged(pos_type start, pos_type end) const
2420 return pimpl_->isChanged(start, end);
2424 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2426 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2430 void Paragraph::setChange(Change const & change)
2432 pimpl_->setChange(change);
2436 void Paragraph::setChange(pos_type pos, Change const & change)
2438 pimpl_->setChange(pos, change);
2442 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2444 return pimpl_->acceptChanges(bparams, start, end);
2448 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2450 return pimpl_->rejectChanges(bparams, start, end);
2454 int Paragraph::id() const
2460 Layout_ptr const & Paragraph::layout() const
2466 void Paragraph::layout(Layout_ptr const & new_layout)
2468 layout_ = new_layout;
2472 Inset * Paragraph::inInset() const
2474 return pimpl_->inset_owner;
2478 Inset::Code Paragraph::ownerCode() const
2480 return pimpl_->inset_owner
2481 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2485 ParagraphParameters & Paragraph::params()
2487 return pimpl_->params;
2491 ParagraphParameters const & Paragraph::params() const
2493 return pimpl_->params;
2497 bool Paragraph::isFreeSpacing() const
2499 if (layout()->free_spacing)
2502 // for now we just need this, later should we need this in some
2503 // other way we can always add a function to Inset too.
2504 return ownerCode() == Inset::ERT_CODE;
2508 bool Paragraph::allowEmpty() const
2510 if (layout()->keepempty)
2512 return ownerCode() == Inset::ERT_CODE;
2516 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2518 if (!Encodings::is_arabic(c))
2521 value_type prev_char = ' ';
2522 value_type next_char = ' ';
2524 for (pos_type i = pos - 1; i >= 0; --i) {
2525 value_type const par_char = getChar(i);
2526 if (!Encodings::isComposeChar_arabic(par_char)) {
2527 prev_char = par_char;
2532 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2533 value_type const par_char = getChar(i);
2534 if (!Encodings::isComposeChar_arabic(par_char)) {
2535 next_char = par_char;
2540 if (Encodings::is_arabic(next_char)) {
2541 if (Encodings::is_arabic(prev_char) &&
2542 !Encodings::is_arabic_special(prev_char))
2543 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2545 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2547 if (Encodings::is_arabic(prev_char) &&
2548 !Encodings::is_arabic_special(prev_char))
2549 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2551 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2556 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2561 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2563 // expand at the end of a row only if there is another hfill on the same row
2564 if (pos == row.endpos() - 1) {
2565 for (pos_type i = row.pos(); i < pos; i++) {
2572 // expand at the beginning of a row only if it is the first row of a paragraph
2573 if (pos == row.pos()) {
2577 // do not expand in some labels
2578 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2581 // if there is anything between the first char of the row and
2582 // the specified position that is neither a newline nor an hfill,
2583 // the hfill will be expanded, otherwise it won't
2584 for (pos_type i = row.pos(); i < pos; i++) {
2585 if (!isNewline(i) && !isHfill(i))
2592 bool Paragraph::checkBiblio(bool track_changes)
2594 // Add bibitem insets if necessary
2595 if (layout()->labeltype != LABEL_BIBLIO)
2598 bool hasbibitem = !insetlist.empty()
2599 // Insist on it being in pos 0
2600 && getChar(0) == Paragraph::META_INSET
2601 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2606 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2607 insertInset(0, static_cast<Inset *>(inset),
2608 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));