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 " << to_utf8(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);
1643 docstring label = expandLabel(tclass[parent], bparams);
1644 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1648 return tclass.counters().counterLabel(fmt);
1652 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1655 params().labelWidthString(docstring());
1656 params().align(LYX_ALIGN_LAYOUT);
1657 params().spacing(Spacing(Spacing::Default));
1661 pos_type Paragraph::beginOfBody() const
1663 return begin_of_body_;
1667 void Paragraph::setBeginOfBody()
1669 if (layout()->labeltype != LABEL_MANUAL) {
1674 // Unroll the first two cycles of the loop
1675 // and remember the previous character to
1676 // remove unnecessary getChar() calls
1678 pos_type end = size();
1679 if (i < end && !isNewline(i)) {
1681 char_type previous_char = 0;
1684 previous_char = text_[i];
1685 if (!isNewline(i)) {
1687 while (i < end && previous_char != ' ') {
1692 previous_char = temp;
1702 // returns -1 if inset not found
1703 int Paragraph::getPositionOfInset(Inset const * inset) const
1706 InsetList::const_iterator it = insetlist.begin();
1707 InsetList::const_iterator end = insetlist.end();
1708 for (; it != end; ++it)
1709 if (it->inset == inset)
1715 InsetBibitem * Paragraph::bibitem() const
1717 if (!insetlist.empty()) {
1718 Inset * inset = insetlist.begin()->inset;
1719 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1720 return static_cast<InsetBibitem *>(inset);
1726 bool Paragraph::forceDefaultParagraphs() const
1728 return inInset() && inInset()->forceDefaultParagraphs(0);
1734 // paragraphs inside floats need different alignment tags to avoid
1737 bool noTrivlistCentering(Inset::Code code)
1739 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1743 string correction(string const & orig)
1745 if (orig == "flushleft")
1746 return "raggedright";
1747 if (orig == "flushright")
1748 return "raggedleft";
1749 if (orig == "center")
1755 string const corrected_env(string const & suffix, string const & env,
1758 string output = suffix + "{";
1759 if (noTrivlistCentering(code))
1760 output += correction(env);
1764 if (suffix == "\\begin")
1770 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1772 if (!contains(str, "\n"))
1773 column += str.size();
1777 column = rsplit(str, tmp, '\n').size();
1784 // This could go to ParagraphParameters if we want to
1785 int Paragraph::startTeXParParams(BufferParams const & bparams,
1786 odocstream & os, TexRow & texrow,
1787 bool moving_arg) const
1791 if (params().noindent()) {
1792 os << "\\noindent ";
1796 LyXAlignment const curAlign = params().align();
1798 if (curAlign == layout()->align)
1802 case LYX_ALIGN_NONE:
1803 case LYX_ALIGN_BLOCK:
1804 case LYX_ALIGN_LAYOUT:
1805 case LYX_ALIGN_SPECIAL:
1807 case LYX_ALIGN_LEFT:
1808 case LYX_ALIGN_RIGHT:
1809 case LYX_ALIGN_CENTER:
1818 case LYX_ALIGN_NONE:
1819 case LYX_ALIGN_BLOCK:
1820 case LYX_ALIGN_LAYOUT:
1821 case LYX_ALIGN_SPECIAL:
1823 case LYX_ALIGN_LEFT: {
1825 if (getParLanguage(bparams)->babel() != "hebrew")
1826 output = corrected_env("\\begin", "flushleft", ownerCode());
1828 output = corrected_env("\\begin", "flushright", ownerCode());
1829 os << from_ascii(output);
1830 adjust_row_column(output, texrow, column);
1832 } case LYX_ALIGN_RIGHT: {
1834 if (getParLanguage(bparams)->babel() != "hebrew")
1835 output = corrected_env("\\begin", "flushright", ownerCode());
1837 output = corrected_env("\\begin", "flushleft", ownerCode());
1838 os << from_ascii(output);
1839 adjust_row_column(output, texrow, column);
1841 } case LYX_ALIGN_CENTER: {
1843 output = corrected_env("\\begin", "center", ownerCode());
1844 os << from_ascii(output);
1845 adjust_row_column(output, texrow, column);
1854 // This could go to ParagraphParameters if we want to
1855 int Paragraph::endTeXParParams(BufferParams const & bparams,
1856 odocstream & os, TexRow & texrow,
1857 bool moving_arg) const
1861 switch (params().align()) {
1862 case LYX_ALIGN_NONE:
1863 case LYX_ALIGN_BLOCK:
1864 case LYX_ALIGN_LAYOUT:
1865 case LYX_ALIGN_SPECIAL:
1867 case LYX_ALIGN_LEFT:
1868 case LYX_ALIGN_RIGHT:
1869 case LYX_ALIGN_CENTER:
1877 switch (params().align()) {
1878 case LYX_ALIGN_NONE:
1879 case LYX_ALIGN_BLOCK:
1880 case LYX_ALIGN_LAYOUT:
1881 case LYX_ALIGN_SPECIAL:
1883 case LYX_ALIGN_LEFT: {
1885 if (getParLanguage(bparams)->babel() != "hebrew")
1886 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1888 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1889 os << from_ascii(output);
1890 adjust_row_column(output, texrow, column);
1892 } case LYX_ALIGN_RIGHT: {
1894 if (getParLanguage(bparams)->babel() != "hebrew")
1895 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1897 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1898 os << from_ascii(output);
1899 adjust_row_column(output, texrow, column);
1901 } case LYX_ALIGN_CENTER: {
1903 output = corrected_env("\n\\par\\end", "center", ownerCode());
1904 os << from_ascii(output);
1905 adjust_row_column(output, texrow, column);
1914 // This one spits out the text of the paragraph
1915 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1916 BufferParams const & bparams,
1917 Font const & outerfont,
1918 odocstream & os, TexRow & texrow,
1919 OutputParams const & runparams) const
1921 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1923 bool return_value = false;
1927 // well we have to check if we are in an inset with unlimited
1928 // length (all in one row) if that is true then we don't allow
1929 // any special options in the paragraph and also we don't allow
1930 // any environment other than the default layout of the text class
1932 bool asdefault = forceDefaultParagraphs();
1935 style = bparams.getTextClass().defaultLayout();
1940 // Current base font for all inherited font changes, without any
1941 // change caused by an individual character, except for the language:
1942 // It is set to the language of the first character.
1943 // As long as we are in the label, this font is the base font of the
1944 // label. Before the first body character it is set to the base font
1948 // Maybe we have to create a optional argument.
1949 pos_type body_pos = beginOfBody();
1950 unsigned int column = 0;
1953 // the optional argument is kept in curly brackets in
1954 // case it contains a ']'
1957 basefont = getLabelFont(bparams, outerfont);
1959 basefont = getLayoutFont(bparams, outerfont);
1962 // Which font is currently active?
1963 Font running_font(basefont);
1964 // Do we have an open font change?
1965 bool open_font = false;
1967 Change runningChange = Change(Change::UNCHANGED);
1969 texrow.start(id(), 0);
1971 // if the paragraph is empty, the loop will not be entered at all
1973 if (style->isCommand()) {
1978 column += startTeXParParams(bparams, os, texrow,
1979 runparams.moving_arg);
1982 for (pos_type i = 0; i < size(); ++i) {
1983 // First char in paragraph or after label?
1984 if (i == body_pos) {
1987 column += running_font.latexWriteEndChanges(
1988 os, bparams, runparams,
1989 basefont, basefont);
1992 basefont = getLayoutFont(bparams, outerfont);
1993 running_font = basefont;
1995 column += Changes::latexMarkChange(os, bparams,
1996 runningChange, Change(Change::UNCHANGED));
1997 runningChange = Change(Change::UNCHANGED);
2002 if (style->isCommand()) {
2008 column += startTeXParParams(bparams, os,
2010 runparams.moving_arg);
2013 Change const & change = pimpl_->lookupChange(i);
2015 if (bparams.outputChanges && runningChange != change) {
2017 column += running_font.latexWriteEndChanges(
2018 os, bparams, runparams, basefont, basefont);
2021 basefont = getLayoutFont(bparams, outerfont);
2022 running_font = basefont;
2024 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2025 runningChange = change;
2028 // do not output text which is marked deleted
2029 // if change tracking output is disabled
2030 if (!bparams.outputChanges && change.type == Change::DELETED) {
2036 value_type const c = getChar(i);
2038 // Fully instantiated font
2039 Font const font = getFont(bparams, i, outerfont);
2041 Font const last_font = running_font;
2043 // Do we need to close the previous font?
2045 (font != running_font ||
2046 font.language() != running_font.language()))
2048 column += running_font.latexWriteEndChanges(
2049 os, bparams, runparams, basefont,
2050 (i == body_pos-1) ? basefont : font);
2051 running_font = basefont;
2055 // Switch file encoding if necessary
2056 if (runparams.encoding->package() == Encoding::inputenc &&
2057 font.language()->encoding()->package() == Encoding::inputenc) {
2058 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2059 runparams.moving_arg, *(runparams.encoding),
2060 *(font.language()->encoding()));
2061 if (enc_switch.first) {
2062 column += enc_switch.second;
2063 runparams.encoding = font.language()->encoding();
2067 // Do we need to change font?
2068 if ((font != running_font ||
2069 font.language() != running_font.language()) &&
2072 column += font.latexWriteStartChanges(os, bparams,
2073 runparams, basefont,
2075 running_font = font;
2080 // Do not print the separation of the optional argument
2081 // if style->pass_thru is false. This works because
2082 // simpleTeXSpecialChars ignores spaces if
2083 // style->pass_thru is false.
2084 if (i != body_pos - 1) {
2085 if (pimpl_->simpleTeXBlanks(
2086 *(runparams.encoding), os, texrow,
2087 i, column, font, *style))
2088 // A surrogate pair was output. We
2089 // must not call simpleTeXSpecialChars
2090 // in this iteration, since
2091 // simpleTeXBlanks incremented i, and
2092 // simpleTeXSpecialChars would output
2093 // the combining character again.
2098 OutputParams rp = runparams;
2099 rp.free_spacing = style->free_spacing;
2100 rp.local_font = &font;
2101 rp.intitle = style->intitle;
2102 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2103 texrow, rp, running_font,
2104 basefont, outerfont, open_font,
2105 runningChange, *style, i, column, c);
2108 // If we have an open font definition, we have to close it
2110 #ifdef FIXED_LANGUAGE_END_DETECTION
2113 .latexWriteEndChanges(os, bparams, runparams,
2115 next_->getFont(bparams, 0, outerfont));
2117 running_font.latexWriteEndChanges(os, bparams,
2118 runparams, basefont, basefont);
2121 #ifdef WITH_WARNINGS
2122 //#warning For now we ALWAYS have to close the foreign font settings if they are
2123 //#warning there as we start another \selectlanguage with the next paragraph if
2124 //#warning we are in need of this. This should be fixed sometime (Jug)
2126 running_font.latexWriteEndChanges(os, bparams, runparams,
2127 basefont, basefont);
2131 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2133 // Needed if there is an optional argument but no contents.
2134 if (body_pos > 0 && body_pos == size()) {
2136 return_value = false;
2140 column += endTeXParParams(bparams, os, texrow,
2141 runparams.moving_arg);
2144 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2145 return return_value;
2162 string tag_name(PAR_TAG const & pt) {
2164 case PAR_NONE: return "!-- --";
2165 case TT: return "tt";
2166 case SF: return "sf";
2167 case BF: return "bf";
2168 case IT: return "it";
2169 case SL: return "sl";
2170 case EM: return "em";
2177 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2179 p1 = static_cast<PAR_TAG>(p1 | p2);
2184 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2186 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2192 bool Paragraph::emptyTag() 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::TOC_CODE &&
2199 lyx_code != Inset::INCLUDE_CODE &&
2200 lyx_code != Inset::GRAPHICS_CODE &&
2201 lyx_code != Inset::ERT_CODE &&
2202 lyx_code != Inset::LISTINGS_CODE &&
2203 lyx_code != Inset::FLOAT_CODE &&
2204 lyx_code != Inset::TABULAR_CODE) {
2208 value_type c = getChar(i);
2209 if (c != ' ' && c != '\t')
2217 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2219 for (pos_type i = 0; i < size(); ++i) {
2221 Inset const * inset = getInset(i);
2222 Inset::Code lyx_code = inset->lyxCode();
2223 if (lyx_code == Inset::LABEL_CODE) {
2224 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2225 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2234 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2237 for (i = 0; i < size(); ++i) {
2239 Inset const * inset = getInset(i);
2240 inset->docbook(buf, os, runparams);
2242 value_type c = getChar(i);
2245 os << sgml::escapeChar(c);
2252 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2256 for (pos_type i = initial; i < size(); ++i) {
2257 Font font = getFont(buf.params(), i, outerfont);
2260 if (i != initial && font != font_old)
2269 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2271 OutputParams const & runparams,
2272 Font const & outerfont,
2273 pos_type initial) const
2275 bool emph_flag = false;
2277 Layout_ptr const & style = layout();
2279 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2281 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2284 // parsing main loop
2285 for (pos_type i = initial; i < size(); ++i) {
2286 Font font = getFont(buf.params(), i, outerfont);
2288 // handle <emphasis> tag
2289 if (font_old.emph() != font.emph()) {
2290 if (font.emph() == Font::ON) {
2293 } else if (i != initial) {
2294 os << "</emphasis>";
2300 Inset const * inset = getInset(i);
2301 inset->docbook(buf, os, runparams);
2303 value_type c = getChar(i);
2305 if (style->pass_thru)
2308 os << sgml::escapeChar(c);
2314 os << "</emphasis>";
2317 if (style->free_spacing)
2319 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2324 bool Paragraph::isNewline(pos_type pos) const
2327 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2331 bool Paragraph::isLineSeparator(pos_type pos) const
2333 value_type const c = getChar(pos);
2334 return isLineSeparatorChar(c)
2335 || (c == Paragraph::META_INSET && getInset(pos) &&
2336 getInset(pos)->isLineSeparator());
2340 /// Used by the spellchecker
2341 bool Paragraph::isLetter(pos_type pos) const
2344 return getInset(pos)->isLetter();
2346 value_type const c = getChar(pos);
2347 return isLetterChar(c) || isDigit(c);
2353 Paragraph::getParLanguage(BufferParams const & bparams) const
2356 return getFirstFontSettings(bparams).language();
2357 #ifdef WITH_WARNINGS
2358 #warning FIXME we should check the prev par as well (Lgb)
2360 return bparams.language;
2364 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2366 return lyxrc.rtl_support
2367 && getParLanguage(bparams)->rightToLeft()
2368 && ownerCode() != Inset::ERT_CODE
2369 && ownerCode() != Inset::LISTINGS_CODE;
2373 void Paragraph::changeLanguage(BufferParams const & bparams,
2374 Language const * from, Language const * to)
2376 // change language including dummy font change at the end
2377 for (pos_type i = 0; i <= size(); ++i) {
2378 Font font = getFontSettings(bparams, i);
2379 if (font.language() == from) {
2380 font.setLanguage(to);
2387 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2389 Language const * doc_language = bparams.language;
2390 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2391 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2393 for (; cit != end; ++cit)
2394 if (cit->font().language() != ignore_language &&
2395 cit->font().language() != latex_language &&
2396 cit->font().language() != doc_language)
2402 // Convert the paragraph to a string.
2403 // Used for building the table of contents
2404 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2406 return asString(buffer, 0, size(), label);
2410 docstring const Paragraph::asString(Buffer const & buffer,
2411 pos_type beg, pos_type end, bool label) const
2414 odocstringstream os;
2416 if (beg == 0 && label && !params().labelString().empty())
2417 os << params().labelString() << ' ';
2419 for (pos_type i = beg; i < end; ++i) {
2420 value_type const c = getChar(i);
2423 else if (c == META_INSET)
2424 getInset(i)->textString(buffer, os);
2431 void Paragraph::setInsetOwner(Inset * inset)
2433 pimpl_->inset_owner = inset;
2437 Change const & Paragraph::lookupChange(pos_type pos) const
2439 BOOST_ASSERT(pos <= size());
2440 return pimpl_->lookupChange(pos);
2444 bool Paragraph::isChanged(pos_type start, pos_type end) const
2446 return pimpl_->isChanged(start, end);
2450 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2452 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2456 void Paragraph::setChange(Change const & change)
2458 pimpl_->setChange(change);
2462 void Paragraph::setChange(pos_type pos, Change const & change)
2464 pimpl_->setChange(pos, change);
2468 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2470 return pimpl_->acceptChanges(bparams, start, end);
2474 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2476 return pimpl_->rejectChanges(bparams, start, end);
2480 int Paragraph::id() const
2486 Layout_ptr const & Paragraph::layout() const
2492 void Paragraph::layout(Layout_ptr const & new_layout)
2494 layout_ = new_layout;
2498 Inset * Paragraph::inInset() const
2500 return pimpl_->inset_owner;
2504 Inset::Code Paragraph::ownerCode() const
2506 return pimpl_->inset_owner
2507 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2511 ParagraphParameters & Paragraph::params()
2513 return pimpl_->params;
2517 ParagraphParameters const & Paragraph::params() const
2519 return pimpl_->params;
2523 bool Paragraph::isFreeSpacing() const
2525 if (layout()->free_spacing)
2528 // for now we just need this, later should we need this in some
2529 // other way we can always add a function to Inset too.
2530 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2534 bool Paragraph::allowEmpty() const
2536 if (layout()->keepempty)
2538 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2542 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2544 if (!Encodings::is_arabic(c))
2547 value_type prev_char = ' ';
2548 value_type next_char = ' ';
2550 for (pos_type i = pos - 1; i >= 0; --i) {
2551 value_type const par_char = getChar(i);
2552 if (!Encodings::isComposeChar_arabic(par_char)) {
2553 prev_char = par_char;
2558 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2559 value_type const par_char = getChar(i);
2560 if (!Encodings::isComposeChar_arabic(par_char)) {
2561 next_char = par_char;
2566 if (Encodings::is_arabic(next_char)) {
2567 if (Encodings::is_arabic(prev_char) &&
2568 !Encodings::is_arabic_special(prev_char))
2569 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2571 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2573 if (Encodings::is_arabic(prev_char) &&
2574 !Encodings::is_arabic_special(prev_char))
2575 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2577 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2582 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2587 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2589 // expand at the end of a row only if there is another hfill on the same row
2590 if (pos == row.endpos() - 1) {
2591 for (pos_type i = row.pos(); i < pos; i++) {
2598 // expand at the beginning of a row only if it is the first row of a paragraph
2599 if (pos == row.pos()) {
2603 // do not expand in some labels
2604 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2607 // if there is anything between the first char of the row and
2608 // the specified position that is neither a newline nor an hfill,
2609 // the hfill will be expanded, otherwise it won't
2610 for (pos_type i = row.pos(); i < pos; i++) {
2611 if (!isNewline(i) && !isHfill(i))
2618 int Paragraph::checkBiblio(bool track_changes)
2621 //This is getting more and more a mess. ...We really should clean
2622 //up this bibitem issue for 1.6. See also bug 2743.
2624 // Add bibitem insets if necessary
2625 if (layout()->labeltype != LABEL_BIBLIO)
2628 bool hasbibitem = !insetlist.empty()
2629 // Insist on it being in pos 0
2630 && getChar(0) == Paragraph::META_INSET
2631 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2636 // remove a bibitem in pos != 0
2637 // restore it later in pos 0 if necessary
2638 // (e.g. if a user inserts contents _before_ the item)
2639 // we're assuming there's only one of these, which there
2641 int erasedInsetPosition = -1;
2642 InsetList::iterator it = insetlist.begin();
2643 InsetList::iterator end = insetlist.end();
2644 for (; it != end; ++it)
2645 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2647 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2648 oldkey = olditem->getParam("key");
2649 oldlabel = olditem->getParam("label");
2650 erasedInsetPosition = it->pos;
2651 eraseChar(erasedInsetPosition, track_changes);
2655 //There was an InsetBibitem at the beginning, and we didn't
2656 //have to erase one.
2657 if (hasbibitem && erasedInsetPosition < 0)
2660 //There was an InsetBibitem at the beginning and we did have to
2661 //erase one. So we give its properties to the beginning inset.
2663 InsetBibitem * inset =
2664 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2665 if (!oldkey.empty())
2666 inset->setParam("key", oldkey);
2667 inset->setParam("label", oldlabel);
2668 return -erasedInsetPosition;
2671 //There was no inset at the beginning, so we need to create one with
2672 //the key and label of the one we erased.
2673 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2674 // restore values of previously deleted item in this par.
2675 if (!oldkey.empty())
2676 inset->setParam("key", oldkey);
2677 inset->setParam("label", oldlabel);
2678 insertInset(0, static_cast<Inset *>(inset),
2679 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2685 void Paragraph::checkAuthors(AuthorList const & authorList)
2687 pimpl_->changes_.checkAuthors(authorList);