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 & 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 & 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, bparams, runparams,
710 if (running_font.family() == Font::TYPEWRITER_FAMILY)
713 basefont = owner_->getLayoutFont(bparams, outerfont);
714 running_font = basefont;
716 if (runparams.moving_arg)
722 texrow.start(owner_->id(), i + 1);
727 if (inset->canTrackChanges()) {
728 column += Changes::latexMarkChange(os, bparams, running_change,
729 Change(Change::UNCHANGED));
730 running_change = Change(Change::UNCHANGED);
734 odocstream::pos_type const len = os.tellp();
736 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
737 || inset->lyxCode() == Inset::MATH_CODE
738 || inset->lyxCode() == Inset::URL_CODE)
739 && running_font.isRightToLeft()) {
740 if (running_font.language()->lang() == "farsi")
748 #warning Bug: we can have an empty font change here!
749 // if there has just been a font change, we are going to close it
750 // right now, which means stupid latex code like \textsf{}. AFAIK,
751 // this does not harm dvi output. A minor bug, thus (JMarc)
753 // some insets cannot be inside a font change command
754 if (open_font && inset->noFontChange()) {
755 column += running_font.latexWriteEndChanges(
756 os, bparams, runparams,
759 basefont = owner_->getLayoutFont(bparams, outerfont);
760 running_font = basefont;
763 int tmp = inset->latex(buf, os, runparams);
766 if (running_font.language()->lang() == "farsi")
773 for (int j = 0; j < tmp; ++j) {
776 texrow.start(owner_->id(), i + 1);
779 column += os.tellp() - len;
785 // And now for the special cases within each mode
789 os << "\\textbackslash{}";
793 case '|': case '<': case '>':
794 // In T1 encoding, these characters exist
795 if (lyxrc.fontenc == "T1") {
797 //... but we should avoid ligatures
798 if ((c == '>' || c == '<')
800 && getChar(i + 1) == c) {
801 //os << "\\textcompwordmark{}";
803 // Jean-Marc, have a look at
804 // this. I think this works
812 // Typewriter font also has them
813 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
817 // Otherwise, we use what LaTeX
821 os << "\\textless{}";
825 os << "\\textgreater{}";
835 case '-': // "--" in Typewriter mode -> "-{}-"
836 if (i <= size() - 2 &&
837 getChar(i + 1) == '-' &&
838 running_font.family() == Font::TYPEWRITER_FAMILY) {
847 os << "\\char`\\\"{}";
852 case '%': case '#': case '{':
860 os << "\\textasciitilde{}";
865 os << "\\textasciicircum{}";
870 // avoid being mistaken for optional arguments
878 // Blanks are printed before font switching.
879 // Sure? I am not! (try nice-latex)
880 // I am sure it's correct. LyX might be smarter
881 // in the future, but for now, nothing wrong is
887 // I assume this is hack treating typewriter as verbatim
888 // FIXME UNICODE: This can fail if c cannot be encoded
889 // in the current encoding.
890 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
899 // FIXME: if we have "LaTeX" with a font
900 // change in the middle (before the 'T', then
901 // the "TeX" part is still special cased.
902 // Really we should only operate this on
903 // "words" for some definition of word
907 for (; pnr < phrases_nr; ++pnr) {
908 if (isTextAt(special_phrases[pnr].phrase, i)) {
909 os << special_phrases[pnr].macro;
910 i += special_phrases[pnr].phrase.length() - 1;
911 column += special_phrases[pnr].macro.length() - 1;
916 if (pnr == phrases_nr && c != '\0') {
917 Encoding const & encoding = *(runparams.encoding);
918 if (i < size() - 1) {
919 char_type next = getChar(i + 1);
920 if (Encodings::isCombiningChar(next)) {
921 column += latexSurrogatePair(os, c, next, encoding) - 1;
926 docstring const latex = encoding.latexChar(c);
927 if (latex.length() > 1 &&
928 latex[latex.length() - 1] != '}') {
929 // Prevent eating of a following
930 // space or command corruption by
931 // following characters
932 column += latex.length() + 1;
935 column += latex.length() - 1;
945 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
946 Layout const & layout) const
948 BufferParams const & bparams = features.bufferParams();
951 if (!params.spacing().isDefault())
952 features.require("setspace");
955 features.useLayout(layout.name());
958 Language const * doc_language = bparams.language;
960 FontList::const_iterator fcit = fontlist.begin();
961 FontList::const_iterator fend = fontlist.end();
962 for (; fcit != fend; ++fcit) {
963 if (fcit->font().noun() == Font::ON) {
964 LYXERR(Debug::LATEX) << "font.noun: "
965 << fcit->font().noun()
967 features.require("noun");
968 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
969 << to_utf8(fcit->font().stateText(0))
972 switch (fcit->font().color()) {
976 // probably we should put here all interface colors used for
977 // font displaying! For now I just add this ones I know of (Jug)
982 features.require("color");
983 LYXERR(Debug::LATEX) << "Color enabled. Font: "
984 << to_utf8(fcit->font().stateText(0))
988 Language const * language = fcit->font().language();
989 if (language->babel() != doc_language->babel() &&
990 language != ignore_language &&
991 language != latex_language)
993 features.useLanguage(language);
994 LYXERR(Debug::LATEX) << "Found language "
995 << language->lang() << endl;
999 if (!params.leftIndent().zero())
1000 features.require("ParagraphLeftIndent");
1003 InsetList::const_iterator icit = owner_->insetlist.begin();
1004 InsetList::const_iterator iend = owner_->insetlist.end();
1005 for (; icit != iend; ++icit) {
1007 icit->inset->validate(features);
1008 if (layout.needprotect &&
1009 icit->inset->lyxCode() == Inset::FOOT_CODE)
1010 features.require("NeedLyXFootnoteCode");
1014 // then the contents
1015 for (pos_type i = 0; i < size() ; ++i) {
1016 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1017 if (!special_phrases[pnr].builtin
1018 && isTextAt(special_phrases[pnr].phrase, i)) {
1019 features.require(special_phrases[pnr].phrase);
1023 Encodings::validate(getChar(i), features);
1031 /////////////////////////////////////////////////////////////////////
1035 /////////////////////////////////////////////////////////////////////
1039 Paragraph::Paragraph()
1040 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1047 Paragraph::Paragraph(Paragraph const & par)
1048 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1049 layout_(par.layout_),
1050 text_(par.text_), begin_of_body_(par.begin_of_body_),
1051 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1053 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1054 InsetList::iterator it = insetlist.begin();
1055 InsetList::iterator end = insetlist.end();
1056 for (; it != end; ++it)
1057 it->inset = it->inset->clone().release();
1061 Paragraph & Paragraph::operator=(Paragraph const & par)
1063 // needed as we will destroy the pimpl_ before copying it
1065 itemdepth = par.itemdepth;
1067 insetlist = par.insetlist;
1068 InsetList::iterator it = insetlist.begin();
1069 InsetList::iterator end = insetlist.end();
1070 for (; it != end; ++it)
1071 it->inset = it->inset->clone().release();
1073 layout_ = par.layout();
1075 begin_of_body_ = par.begin_of_body_;
1078 pimpl_ = new Pimpl(*par.pimpl_, this);
1084 Paragraph::~Paragraph()
1088 //lyxerr << "Paragraph::paragraph_id = "
1089 // << Paragraph::paragraph_id << endl;
1093 void Paragraph::write(Buffer const & buf, ostream & os,
1094 BufferParams const & bparams,
1095 depth_type & dth) const
1097 // The beginning or end of a deeper (i.e. nested) area?
1098 if (dth != params().depth()) {
1099 if (params().depth() > dth) {
1100 while (params().depth() > dth) {
1101 os << "\n\\begin_deeper";
1105 while (params().depth() < dth) {
1106 os << "\n\\end_deeper";
1112 // First write the layout
1113 os << "\n\\begin_layout " << layout()->name() << '\n';
1117 Font font1(Font::ALL_INHERIT, bparams.language);
1119 Change running_change = Change(Change::UNCHANGED);
1122 for (pos_type i = 0; i <= size(); ++i) {
1124 Change change = pimpl_->lookupChange(i);
1125 Changes::lyxMarkChange(os, column, running_change, change);
1126 running_change = change;
1131 // Write font changes
1132 Font font2 = getFontSettings(bparams, i);
1133 if (font2 != font1) {
1134 font2.lyxWriteChanges(font1, os);
1139 value_type const c = getChar(i);
1143 Inset const * inset = getInset(i);
1145 if (inset->directWrite()) {
1146 // international char, let it write
1147 // code directly so it's shorter in
1149 inset->write(buf, os);
1153 os << "\\begin_inset ";
1154 inset->write(buf, os);
1155 os << "\n\\end_inset\n\n";
1161 os << "\n\\backslash\n";
1165 if (i + 1 < size() && getChar(i + 1) == ' ') {
1172 if ((column > 70 && c == ' ')
1177 // this check is to amend a bug. LyX sometimes
1178 // inserts '\0' this could cause problems.
1180 std::vector<char> tmp = ucs4_to_utf8(c);
1181 tmp.push_back('\0');
1184 lyxerr << "ERROR (Paragraph::writeFile):"
1185 " NULL char in structure." << endl;
1191 os << "\n\\end_layout\n";
1195 void Paragraph::validate(LaTeXFeatures & features) const
1197 pimpl_->validate(features, *layout());
1201 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1203 return pimpl_->eraseChar(pos, trackChanges);
1207 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1209 return pimpl_->eraseChars(start, end, trackChanges);
1213 void Paragraph::insert(pos_type start, docstring const & str,
1214 Font const & font, Change const & change)
1216 for (size_t i = 0, n = str.size(); i != n ; ++i)
1217 insertChar(start + i, str[i], font, change);
1221 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1224 pimpl_->insertChar(pos, c, Change(trackChanges ?
1225 Change::INSERTED : Change::UNCHANGED));
1229 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1230 Font const & font, bool trackChanges)
1232 pimpl_->insertChar(pos, c, Change(trackChanges ?
1233 Change::INSERTED : Change::UNCHANGED));
1238 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1239 Font const & font, Change const & change)
1241 pimpl_->insertChar(pos, c, change);
1246 void Paragraph::insertInset(pos_type pos, Inset * inset,
1247 Change const & change)
1249 pimpl_->insertInset(pos, inset, change);
1253 void Paragraph::insertInset(pos_type pos, Inset * inset,
1254 Font const & font, Change const & change)
1256 pimpl_->insertInset(pos, inset, change);
1257 // Set the font/language of the inset...
1259 // ... as well as the font/language of the text inside the inset
1260 // FIXME: This is far from perfect. It basically overrides work being done
1261 // in the InsetText constructor. Also, it doesn't work for Tables
1262 // (precisely because each cell's font/language is set in the Table's
1263 // constructor, so by now it's too late). The long-term solution should
1264 // be moving current_font into Cursor, and getting rid of all this...
1265 // (see http://thread.gmane.org/gmane.editors.lyx.devel/88869/focus=88944)
1266 if (inset->asTextInset()) {
1267 inset->asTextInset()->text_.current_font = font;
1268 inset->asTextInset()->text_.real_current_font = font;
1273 bool Paragraph::insetAllowed(Inset_code code)
1275 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1279 // Gets uninstantiated font setting at position.
1280 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1284 lyxerr << " pos: " << pos << " size: " << size() << endl;
1285 BOOST_ASSERT(pos <= size());
1288 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1289 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1290 for (; cit != end; ++cit)
1291 if (cit->pos() >= pos)
1297 if (pos == size() && !empty())
1298 return getFontSettings(bparams, pos - 1);
1300 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1304 FontSpan Paragraph::fontSpan(pos_type pos) const
1306 BOOST_ASSERT(pos <= size());
1309 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1310 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1311 for (; cit != end; ++cit) {
1312 if (cit->pos() >= pos) {
1313 if (pos >= beginOfBody())
1314 return FontSpan(std::max(start, beginOfBody()),
1317 return FontSpan(start,
1318 std::min(beginOfBody() - 1,
1321 start = cit->pos() + 1;
1324 // This should not happen, but if so, we take no chances.
1325 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1327 return FontSpan(pos, pos);
1331 // Gets uninstantiated font setting at position 0
1332 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1334 if (!empty() && !pimpl_->fontlist.empty())
1335 return pimpl_->fontlist[0].font();
1337 return Font(Font::ALL_INHERIT, bparams.language);
1341 // Gets the fully instantiated font at a given position in a paragraph
1342 // This is basically the same function as Text::GetFont() in text2.cpp.
1343 // The difference is that this one is used for generating the LaTeX file,
1344 // and thus cosmetic "improvements" are disallowed: This has to deliver
1345 // the true picture of the buffer. (Asger)
1346 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1347 Font const & outerfont) const
1349 BOOST_ASSERT(pos >= 0);
1351 Layout_ptr const & lout = layout();
1353 pos_type const body_pos = beginOfBody();
1357 layoutfont = lout->labelfont;
1359 layoutfont = lout->font;
1361 Font font = getFontSettings(bparams, pos);
1362 font.realize(layoutfont);
1363 font.realize(outerfont);
1364 font.realize(bparams.getFont());
1370 Font const Paragraph::getLabelFont
1371 (BufferParams const & bparams, Font const & outerfont) const
1373 Font tmpfont = layout()->labelfont;
1374 tmpfont.setLanguage(getParLanguage(bparams));
1375 tmpfont.realize(outerfont);
1376 tmpfont.realize(bparams.getFont());
1381 Font const Paragraph::getLayoutFont
1382 (BufferParams const & bparams, Font const & outerfont) const
1384 Font tmpfont = layout()->font;
1385 tmpfont.setLanguage(getParLanguage(bparams));
1386 tmpfont.realize(outerfont);
1387 tmpfont.realize(bparams.getFont());
1392 /// Returns the height of the highest font in range
1393 Font_size Paragraph::highestFontInRange
1394 (pos_type startpos, pos_type endpos, Font_size def_size) const
1396 if (pimpl_->fontlist.empty())
1399 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1400 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1401 for (; end_it != end; ++end_it) {
1402 if (end_it->pos() >= endpos)
1409 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1410 for (; cit != end; ++cit) {
1411 if (cit->pos() >= startpos)
1415 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1416 for (; cit != end_it; ++cit) {
1417 Font::FONT_SIZE size = cit->font().size();
1418 if (size == Font::INHERIT_SIZE)
1420 if (size > maxsize && size <= Font::SIZE_HUGER)
1427 Paragraph::value_type
1428 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1430 value_type c = getChar(pos);
1431 if (!lyxrc.rtl_support)
1461 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1468 void Paragraph::setFont(pos_type pos, Font const & font)
1470 BOOST_ASSERT(pos <= size());
1472 // First, reduce font against layout/label font
1473 // Update: The setCharFont() routine in text2.cpp already
1474 // reduces font, so we don't need to do that here. (Asger)
1475 // No need to simplify this because it will disappear
1476 // in a new kernel. (Asger)
1477 // Next search font table
1479 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1480 Pimpl::FontList::iterator it = beg;
1481 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1482 for (; it != endit; ++it) {
1483 if (it->pos() >= pos)
1486 size_t const i = distance(beg, it);
1487 bool notfound = (it == endit);
1489 if (!notfound && pimpl_->fontlist[i].font() == font)
1492 bool begin = pos == 0 || notfound ||
1493 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1494 // Is position pos is a beginning of a font block?
1495 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1496 // Is position pos is the end of a font block?
1497 if (begin && end) { // A single char block
1498 if (i + 1 < pimpl_->fontlist.size() &&
1499 pimpl_->fontlist[i + 1].font() == font) {
1500 // Merge the singleton block with the next block
1501 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1502 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1503 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1504 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1505 // Merge the singleton block with the previous block
1506 pimpl_->fontlist[i - 1].pos(pos);
1507 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1509 pimpl_->fontlist[i].font(font);
1511 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1512 pimpl_->fontlist[i - 1].pos(pos);
1514 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1515 Pimpl::FontTable(pos, font));
1517 pimpl_->fontlist[i].pos(pos - 1);
1518 if (!(i + 1 < pimpl_->fontlist.size() &&
1519 pimpl_->fontlist[i + 1].font() == font))
1520 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1521 Pimpl::FontTable(pos, font));
1522 } else { // The general case. The block is splitted into 3 blocks
1523 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1524 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1525 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1526 Pimpl::FontTable(pos, font));
1531 void Paragraph::makeSameLayout(Paragraph const & par)
1533 layout(par.layout());
1535 params() = par.params();
1539 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1541 if (isFreeSpacing())
1547 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1548 if (eraseChar(pos, trackChanges))
1554 return count > 0 || pos > 0;
1558 bool Paragraph::hasSameLayout(Paragraph const & par) const
1560 return par.layout() == layout() && params().sameLayout(par.params());
1564 depth_type Paragraph::getDepth() const
1566 return params().depth();
1570 depth_type Paragraph::getMaxDepthAfter() const
1572 if (layout()->isEnvironment())
1573 return params().depth() + 1;
1575 return params().depth();
1579 char Paragraph::getAlign() const
1581 if (params().align() == LYX_ALIGN_LAYOUT)
1582 return layout()->align;
1584 return params().align();
1588 docstring const & Paragraph::getLabelstring() const
1590 return params().labelString();
1594 // the next two functions are for the manual labels
1595 docstring const Paragraph::getLabelWidthString() const
1597 if (!params().labelWidthString().empty())
1598 return params().labelWidthString();
1600 return _("Senseless with this layout!");
1604 void Paragraph::setLabelWidthString(docstring const & s)
1606 params().labelWidthString(s);
1610 docstring const Paragraph::translateIfPossible(docstring const & s,
1611 BufferParams const & bparams) const
1613 if (!support::isAscii(s) || s.empty()) {
1614 // This must be a user defined layout. We cannot translate
1615 // this, since gettext accepts only ascii keys.
1618 // Probably standard layout, try to translate
1619 Messages & m = getMessages(getParLanguage(bparams)->code());
1620 return m.get(to_ascii(s));
1624 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1625 BufferParams const & bparams, bool process_appendix) const
1627 TextClass const & tclass = bparams.getTextClass();
1630 if (process_appendix && params().appendix())
1631 fmt = translateIfPossible(layout->labelstring_appendix(),
1634 fmt = translateIfPossible(layout->labelstring(), bparams);
1636 // handle 'inherited level parts' in 'fmt',
1637 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1638 size_t const i = fmt.find('@', 0);
1639 if (i != docstring::npos) {
1640 size_t const j = fmt.find('@', i + 1);
1641 if (j != docstring::npos) {
1642 docstring parent(fmt, i + 1, j - i - 1);
1644 docstring label = expandLabel(tclass[to_utf8(parent)], bparams);
1645 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1649 return tclass.counters().counterLabel(fmt);
1653 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1656 params().labelWidthString(docstring());
1657 params().align(LYX_ALIGN_LAYOUT);
1658 params().spacing(Spacing(Spacing::Default));
1662 pos_type Paragraph::beginOfBody() const
1664 return begin_of_body_;
1668 void Paragraph::setBeginOfBody()
1670 if (layout()->labeltype != LABEL_MANUAL) {
1675 // Unroll the first two cycles of the loop
1676 // and remember the previous character to
1677 // remove unnecessary getChar() calls
1679 pos_type end = size();
1680 if (i < end && !isNewline(i)) {
1682 char_type previous_char = 0;
1685 previous_char = text_[i];
1686 if (!isNewline(i)) {
1688 while (i < end && previous_char != ' ') {
1693 previous_char = temp;
1703 // returns -1 if inset not found
1704 int Paragraph::getPositionOfInset(Inset const * inset) const
1707 InsetList::const_iterator it = insetlist.begin();
1708 InsetList::const_iterator end = insetlist.end();
1709 for (; it != end; ++it)
1710 if (it->inset == inset)
1716 InsetBibitem * Paragraph::bibitem() const
1718 if (!insetlist.empty()) {
1719 Inset * inset = insetlist.begin()->inset;
1720 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1721 return static_cast<InsetBibitem *>(inset);
1727 bool Paragraph::forceDefaultParagraphs() const
1729 return inInset() && inInset()->forceDefaultParagraphs(0);
1735 // paragraphs inside floats need different alignment tags to avoid
1738 bool noTrivlistCentering(Inset::Code code)
1740 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1744 string correction(string const & orig)
1746 if (orig == "flushleft")
1747 return "raggedright";
1748 if (orig == "flushright")
1749 return "raggedleft";
1750 if (orig == "center")
1756 string const corrected_env(string const & suffix, string const & env,
1759 string output = suffix + "{";
1760 if (noTrivlistCentering(code))
1761 output += correction(env);
1765 if (suffix == "\\begin")
1771 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1773 if (!contains(str, "\n"))
1774 column += str.size();
1778 column = rsplit(str, tmp, '\n').size();
1785 // This could go to ParagraphParameters if we want to
1786 int Paragraph::startTeXParParams(BufferParams const & bparams,
1787 odocstream & os, TexRow & texrow,
1788 bool moving_arg) const
1792 if (params().noindent()) {
1793 os << "\\noindent ";
1797 LyXAlignment const curAlign = params().align();
1799 if (curAlign == layout()->align)
1803 case LYX_ALIGN_NONE:
1804 case LYX_ALIGN_BLOCK:
1805 case LYX_ALIGN_LAYOUT:
1806 case LYX_ALIGN_SPECIAL:
1808 case LYX_ALIGN_LEFT:
1809 case LYX_ALIGN_RIGHT:
1810 case LYX_ALIGN_CENTER:
1819 case LYX_ALIGN_NONE:
1820 case LYX_ALIGN_BLOCK:
1821 case LYX_ALIGN_LAYOUT:
1822 case LYX_ALIGN_SPECIAL:
1824 case LYX_ALIGN_LEFT: {
1826 if (getParLanguage(bparams)->babel() != "hebrew")
1827 output = corrected_env("\\begin", "flushleft", ownerCode());
1829 output = corrected_env("\\begin", "flushright", ownerCode());
1830 os << from_ascii(output);
1831 adjust_row_column(output, texrow, column);
1833 } case LYX_ALIGN_RIGHT: {
1835 if (getParLanguage(bparams)->babel() != "hebrew")
1836 output = corrected_env("\\begin", "flushright", ownerCode());
1838 output = corrected_env("\\begin", "flushleft", ownerCode());
1839 os << from_ascii(output);
1840 adjust_row_column(output, texrow, column);
1842 } case LYX_ALIGN_CENTER: {
1844 output = corrected_env("\\begin", "center", ownerCode());
1845 os << from_ascii(output);
1846 adjust_row_column(output, texrow, column);
1855 // This could go to ParagraphParameters if we want to
1856 int Paragraph::endTeXParParams(BufferParams const & bparams,
1857 odocstream & os, TexRow & texrow,
1858 bool moving_arg) const
1862 switch (params().align()) {
1863 case LYX_ALIGN_NONE:
1864 case LYX_ALIGN_BLOCK:
1865 case LYX_ALIGN_LAYOUT:
1866 case LYX_ALIGN_SPECIAL:
1868 case LYX_ALIGN_LEFT:
1869 case LYX_ALIGN_RIGHT:
1870 case LYX_ALIGN_CENTER:
1878 switch (params().align()) {
1879 case LYX_ALIGN_NONE:
1880 case LYX_ALIGN_BLOCK:
1881 case LYX_ALIGN_LAYOUT:
1882 case LYX_ALIGN_SPECIAL:
1884 case LYX_ALIGN_LEFT: {
1886 if (getParLanguage(bparams)->babel() != "hebrew")
1887 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1889 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1890 os << from_ascii(output);
1891 adjust_row_column(output, texrow, column);
1893 } case LYX_ALIGN_RIGHT: {
1895 if (getParLanguage(bparams)->babel() != "hebrew")
1896 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1898 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1899 os << from_ascii(output);
1900 adjust_row_column(output, texrow, column);
1902 } case LYX_ALIGN_CENTER: {
1904 output = corrected_env("\n\\par\\end", "center", ownerCode());
1905 os << from_ascii(output);
1906 adjust_row_column(output, texrow, column);
1915 // This one spits out the text of the paragraph
1916 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1917 BufferParams const & bparams,
1918 Font const & outerfont,
1919 odocstream & os, TexRow & texrow,
1920 OutputParams const & runparams) const
1922 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1924 bool return_value = false;
1928 // well we have to check if we are in an inset with unlimited
1929 // length (all in one row) if that is true then we don't allow
1930 // any special options in the paragraph and also we don't allow
1931 // any environment other than the default layout of the text class
1933 bool asdefault = forceDefaultParagraphs();
1936 style = bparams.getTextClass().defaultLayout();
1941 // Current base font for all inherited font changes, without any
1942 // change caused by an individual character, except for the language:
1943 // It is set to the language of the first character.
1944 // As long as we are in the label, this font is the base font of the
1945 // label. Before the first body character it is set to the base font
1949 // Maybe we have to create a optional argument.
1950 pos_type body_pos = beginOfBody();
1951 unsigned int column = 0;
1954 // the optional argument is kept in curly brackets in
1955 // case it contains a ']'
1958 basefont = getLabelFont(bparams, outerfont);
1960 basefont = getLayoutFont(bparams, outerfont);
1963 // Which font is currently active?
1964 Font running_font(basefont);
1965 // Do we have an open font change?
1966 bool open_font = false;
1968 Change runningChange = Change(Change::UNCHANGED);
1970 texrow.start(id(), 0);
1972 // if the paragraph is empty, the loop will not be entered at all
1974 if (style->isCommand()) {
1979 column += startTeXParParams(bparams, os, texrow,
1980 runparams.moving_arg);
1983 for (pos_type i = 0; i < size(); ++i) {
1984 // First char in paragraph or after label?
1985 if (i == body_pos) {
1988 column += running_font.latexWriteEndChanges(
1989 os, bparams, runparams,
1990 basefont, basefont);
1993 basefont = getLayoutFont(bparams, outerfont);
1994 running_font = basefont;
1996 column += Changes::latexMarkChange(os, bparams,
1997 runningChange, Change(Change::UNCHANGED));
1998 runningChange = Change(Change::UNCHANGED);
2003 if (style->isCommand()) {
2009 column += startTeXParParams(bparams, os,
2011 runparams.moving_arg);
2014 Change const & change = pimpl_->lookupChange(i);
2016 if (bparams.outputChanges && runningChange != change) {
2018 column += running_font.latexWriteEndChanges(
2019 os, bparams, runparams, basefont, basefont);
2022 basefont = getLayoutFont(bparams, outerfont);
2023 running_font = basefont;
2025 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2026 runningChange = change;
2029 // do not output text which is marked deleted
2030 // if change tracking output is disabled
2031 if (!bparams.outputChanges && change.type == Change::DELETED) {
2037 value_type const c = getChar(i);
2039 // Fully instantiated font
2040 Font const font = getFont(bparams, i, outerfont);
2042 Font const last_font = running_font;
2044 // Do we need to close the previous font?
2046 (font != running_font ||
2047 font.language() != running_font.language()))
2049 column += running_font.latexWriteEndChanges(
2050 os, bparams, runparams, basefont,
2051 (i == body_pos-1) ? basefont : font);
2052 running_font = basefont;
2056 // Switch file encoding if necessary
2057 if (runparams.encoding->package() == Encoding::inputenc &&
2058 font.language()->encoding()->package() == Encoding::inputenc) {
2059 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2060 runparams.moving_arg, *(runparams.encoding),
2061 *(font.language()->encoding()));
2062 if (enc_switch.first) {
2063 column += enc_switch.second;
2064 runparams.encoding = font.language()->encoding();
2068 // Do we need to change font?
2069 if ((font != running_font ||
2070 font.language() != running_font.language()) &&
2073 column += font.latexWriteStartChanges(os, bparams,
2074 runparams, basefont,
2076 running_font = font;
2081 // Do not print the separation of the optional argument
2082 // if style->pass_thru is false. This works because
2083 // simpleTeXSpecialChars ignores spaces if
2084 // style->pass_thru is false.
2085 if (i != body_pos - 1) {
2086 if (pimpl_->simpleTeXBlanks(
2087 *(runparams.encoding), os, texrow,
2088 i, column, font, *style))
2089 // A surrogate pair was output. We
2090 // must not call simpleTeXSpecialChars
2091 // in this iteration, since
2092 // simpleTeXBlanks incremented i, and
2093 // simpleTeXSpecialChars would output
2094 // the combining character again.
2099 OutputParams rp = runparams;
2100 rp.free_spacing = style->free_spacing;
2101 rp.local_font = &font;
2102 rp.intitle = style->intitle;
2103 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2104 texrow, rp, running_font,
2105 basefont, outerfont, open_font,
2106 runningChange, *style, i, column, c);
2109 // If we have an open font definition, we have to close it
2111 #ifdef FIXED_LANGUAGE_END_DETECTION
2114 .latexWriteEndChanges(os, bparams, runparams,
2116 next_->getFont(bparams, 0, outerfont));
2118 running_font.latexWriteEndChanges(os, bparams,
2119 runparams, basefont, basefont);
2122 #ifdef WITH_WARNINGS
2123 //#warning For now we ALWAYS have to close the foreign font settings if they are
2124 //#warning there as we start another \selectlanguage with the next paragraph if
2125 //#warning we are in need of this. This should be fixed sometime (Jug)
2127 running_font.latexWriteEndChanges(os, bparams, runparams,
2128 basefont, basefont);
2132 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2134 // Needed if there is an optional argument but no contents.
2135 if (body_pos > 0 && body_pos == size()) {
2137 return_value = false;
2141 column += endTeXParParams(bparams, os, texrow,
2142 runparams.moving_arg);
2145 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2146 return return_value;
2163 string tag_name(PAR_TAG const & pt) {
2165 case PAR_NONE: return "!-- --";
2166 case TT: return "tt";
2167 case SF: return "sf";
2168 case BF: return "bf";
2169 case IT: return "it";
2170 case SL: return "sl";
2171 case EM: return "em";
2178 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2180 p1 = static_cast<PAR_TAG>(p1 | p2);
2185 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2187 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2193 bool Paragraph::emptyTag() const
2195 for (pos_type i = 0; i < size(); ++i) {
2197 Inset const * inset = getInset(i);
2198 Inset::Code lyx_code = inset->lyxCode();
2199 if (lyx_code != Inset::TOC_CODE &&
2200 lyx_code != Inset::INCLUDE_CODE &&
2201 lyx_code != Inset::GRAPHICS_CODE &&
2202 lyx_code != Inset::ERT_CODE &&
2203 lyx_code != Inset::LISTINGS_CODE &&
2204 lyx_code != Inset::FLOAT_CODE &&
2205 lyx_code != Inset::TABULAR_CODE) {
2209 value_type c = getChar(i);
2210 if (c != ' ' && c != '\t')
2218 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2220 for (pos_type i = 0; i < size(); ++i) {
2222 Inset const * inset = getInset(i);
2223 Inset::Code lyx_code = inset->lyxCode();
2224 if (lyx_code == Inset::LABEL_CODE) {
2225 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2226 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2235 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2238 for (i = 0; i < size(); ++i) {
2240 Inset const * inset = getInset(i);
2241 inset->docbook(buf, os, runparams);
2243 value_type c = getChar(i);
2246 os << sgml::escapeChar(c);
2253 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2257 for (pos_type i = initial; i < size(); ++i) {
2258 Font font = getFont(buf.params(), i, outerfont);
2261 if (i != initial && font != font_old)
2270 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2272 OutputParams const & runparams,
2273 Font const & outerfont,
2274 pos_type initial) const
2276 bool emph_flag = false;
2278 Layout_ptr const & style = layout();
2280 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2282 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2285 // parsing main loop
2286 for (pos_type i = initial; i < size(); ++i) {
2287 Font font = getFont(buf.params(), i, outerfont);
2289 // handle <emphasis> tag
2290 if (font_old.emph() != font.emph()) {
2291 if (font.emph() == Font::ON) {
2294 } else if (i != initial) {
2295 os << "</emphasis>";
2301 Inset const * inset = getInset(i);
2302 inset->docbook(buf, os, runparams);
2304 value_type c = getChar(i);
2306 if (style->pass_thru)
2309 os << sgml::escapeChar(c);
2315 os << "</emphasis>";
2318 if (style->free_spacing)
2320 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2325 bool Paragraph::isNewline(pos_type pos) const
2328 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2332 bool Paragraph::isLineSeparator(pos_type pos) const
2334 value_type const c = getChar(pos);
2335 return isLineSeparatorChar(c)
2336 || (c == Paragraph::META_INSET && getInset(pos) &&
2337 getInset(pos)->isLineSeparator());
2341 /// Used by the spellchecker
2342 bool Paragraph::isLetter(pos_type pos) const
2345 return getInset(pos)->isLetter();
2347 value_type const c = getChar(pos);
2348 return isLetterChar(c) || isDigit(c);
2354 Paragraph::getParLanguage(BufferParams const & bparams) const
2357 return getFirstFontSettings(bparams).language();
2358 #ifdef WITH_WARNINGS
2359 #warning FIXME we should check the prev par as well (Lgb)
2361 return bparams.language;
2365 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2367 return lyxrc.rtl_support
2368 && getParLanguage(bparams)->rightToLeft()
2369 && ownerCode() != Inset::ERT_CODE
2370 && ownerCode() != Inset::LISTINGS_CODE;
2374 void Paragraph::changeLanguage(BufferParams const & bparams,
2375 Language const * from, Language const * to)
2377 // change language including dummy font change at the end
2378 for (pos_type i = 0; i <= size(); ++i) {
2379 Font font = getFontSettings(bparams, i);
2380 if (font.language() == from) {
2381 font.setLanguage(to);
2388 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2390 Language const * doc_language = bparams.language;
2391 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2392 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2394 for (; cit != end; ++cit)
2395 if (cit->font().language() != ignore_language &&
2396 cit->font().language() != latex_language &&
2397 cit->font().language() != doc_language)
2403 // Convert the paragraph to a string.
2404 // Used for building the table of contents
2405 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2407 return asString(buffer, 0, size(), label);
2411 docstring const Paragraph::asString(Buffer const & buffer,
2412 pos_type beg, pos_type end, bool label) const
2415 odocstringstream os;
2417 if (beg == 0 && label && !params().labelString().empty())
2418 os << params().labelString() << ' ';
2420 for (pos_type i = beg; i < end; ++i) {
2421 value_type const c = getChar(i);
2424 else if (c == META_INSET)
2425 getInset(i)->textString(buffer, os);
2432 void Paragraph::setInsetOwner(Inset * inset)
2434 pimpl_->inset_owner = inset;
2438 Change const & Paragraph::lookupChange(pos_type pos) const
2440 BOOST_ASSERT(pos <= size());
2441 return pimpl_->lookupChange(pos);
2445 bool Paragraph::isChanged(pos_type start, pos_type end) const
2447 return pimpl_->isChanged(start, end);
2451 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2453 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2457 void Paragraph::setChange(Change const & change)
2459 pimpl_->setChange(change);
2463 void Paragraph::setChange(pos_type pos, Change const & change)
2465 pimpl_->setChange(pos, change);
2469 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2471 return pimpl_->acceptChanges(bparams, start, end);
2475 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2477 return pimpl_->rejectChanges(bparams, start, end);
2481 int Paragraph::id() const
2487 Layout_ptr const & Paragraph::layout() const
2493 void Paragraph::layout(Layout_ptr const & new_layout)
2495 layout_ = new_layout;
2499 Inset * Paragraph::inInset() const
2501 return pimpl_->inset_owner;
2505 Inset::Code Paragraph::ownerCode() const
2507 return pimpl_->inset_owner
2508 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2512 ParagraphParameters & Paragraph::params()
2514 return pimpl_->params;
2518 ParagraphParameters const & Paragraph::params() const
2520 return pimpl_->params;
2524 bool Paragraph::isFreeSpacing() const
2526 if (layout()->free_spacing)
2529 // for now we just need this, later should we need this in some
2530 // other way we can always add a function to Inset too.
2531 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2535 bool Paragraph::allowEmpty() const
2537 if (layout()->keepempty)
2539 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2543 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2545 if (!Encodings::is_arabic(c))
2548 value_type prev_char = ' ';
2549 value_type next_char = ' ';
2551 for (pos_type i = pos - 1; i >= 0; --i) {
2552 value_type const par_char = getChar(i);
2553 if (!Encodings::isComposeChar_arabic(par_char)) {
2554 prev_char = par_char;
2559 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2560 value_type const par_char = getChar(i);
2561 if (!Encodings::isComposeChar_arabic(par_char)) {
2562 next_char = par_char;
2567 if (Encodings::is_arabic(next_char)) {
2568 if (Encodings::is_arabic(prev_char) &&
2569 !Encodings::is_arabic_special(prev_char))
2570 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2572 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2574 if (Encodings::is_arabic(prev_char) &&
2575 !Encodings::is_arabic_special(prev_char))
2576 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2578 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2583 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2588 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2590 // expand at the end of a row only if there is another hfill on the same row
2591 if (pos == row.endpos() - 1) {
2592 for (pos_type i = row.pos(); i < pos; i++) {
2599 // expand at the beginning of a row only if it is the first row of a paragraph
2600 if (pos == row.pos()) {
2604 // do not expand in some labels
2605 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2608 // if there is anything between the first char of the row and
2609 // the specified position that is neither a newline nor an hfill,
2610 // the hfill will be expanded, otherwise it won't
2611 for (pos_type i = row.pos(); i < pos; i++) {
2612 if (!isNewline(i) && !isHfill(i))
2619 int Paragraph::checkBiblio(bool track_changes)
2622 //This is getting more and more a mess. ...We really should clean
2623 //up this bibitem issue for 1.6. See also bug 2743.
2625 // Add bibitem insets if necessary
2626 if (layout()->labeltype != LABEL_BIBLIO)
2629 bool hasbibitem = !insetlist.empty()
2630 // Insist on it being in pos 0
2631 && getChar(0) == Paragraph::META_INSET
2632 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2637 // remove a bibitem in pos != 0
2638 // restore it later in pos 0 if necessary
2639 // (e.g. if a user inserts contents _before_ the item)
2640 // we're assuming there's only one of these, which there
2642 int erasedInsetPosition = -1;
2643 InsetList::iterator it = insetlist.begin();
2644 InsetList::iterator end = insetlist.end();
2645 for (; it != end; ++it)
2646 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2648 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2649 oldkey = olditem->getParam("key");
2650 oldlabel = olditem->getParam("label");
2651 erasedInsetPosition = it->pos;
2652 eraseChar(erasedInsetPosition, track_changes);
2656 //There was an InsetBibitem at the beginning, and we didn't
2657 //have to erase one.
2658 if (hasbibitem && erasedInsetPosition < 0)
2661 //There was an InsetBibitem at the beginning and we did have to
2662 //erase one. So we give its properties to the beginning inset.
2664 InsetBibitem * inset =
2665 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2666 if (!oldkey.empty())
2667 inset->setParam("key", oldkey);
2668 inset->setParam("label", oldlabel);
2669 return -erasedInsetPosition;
2672 //There was no inset at the beginning, so we need to create one with
2673 //the key and label of the one we erased.
2674 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2675 // restore values of previously deleted item in this par.
2676 if (!oldkey.empty())
2677 inset->setParam("key", oldkey);
2678 inset->setParam("label", oldlabel);
2679 insertInset(0, static_cast<Inset *>(inset),
2680 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2686 void Paragraph::checkAuthors(AuthorList const & authorList)
2688 pimpl_->changes_.checkAuthors(authorList);