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);
1261 bool Paragraph::insetAllowed(Inset_code code)
1263 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1267 // Gets uninstantiated font setting at position.
1268 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1272 lyxerr << " pos: " << pos << " size: " << size() << endl;
1273 BOOST_ASSERT(pos <= size());
1276 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1277 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1278 for (; cit != end; ++cit)
1279 if (cit->pos() >= pos)
1285 if (pos == size() && !empty())
1286 return getFontSettings(bparams, pos - 1);
1288 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1292 FontSpan Paragraph::fontSpan(pos_type pos) const
1294 BOOST_ASSERT(pos <= size());
1297 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1298 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1299 for (; cit != end; ++cit) {
1300 if (cit->pos() >= pos) {
1301 if (pos >= beginOfBody())
1302 return FontSpan(std::max(start, beginOfBody()),
1305 return FontSpan(start,
1306 std::min(beginOfBody() - 1,
1309 start = cit->pos() + 1;
1312 // This should not happen, but if so, we take no chances.
1313 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1315 return FontSpan(pos, pos);
1319 // Gets uninstantiated font setting at position 0
1320 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1322 if (!empty() && !pimpl_->fontlist.empty())
1323 return pimpl_->fontlist[0].font();
1325 return Font(Font::ALL_INHERIT, bparams.language);
1329 // Gets the fully instantiated font at a given position in a paragraph
1330 // This is basically the same function as Text::GetFont() in text2.cpp.
1331 // The difference is that this one is used for generating the LaTeX file,
1332 // and thus cosmetic "improvements" are disallowed: This has to deliver
1333 // the true picture of the buffer. (Asger)
1334 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1335 Font const & outerfont) const
1337 BOOST_ASSERT(pos >= 0);
1339 Layout_ptr const & lout = layout();
1341 pos_type const body_pos = beginOfBody();
1345 layoutfont = lout->labelfont;
1347 layoutfont = lout->font;
1349 Font font = getFontSettings(bparams, pos);
1350 font.realize(layoutfont);
1351 font.realize(outerfont);
1352 font.realize(bparams.getFont());
1358 Font const Paragraph::getLabelFont
1359 (BufferParams const & bparams, Font const & outerfont) const
1361 Font tmpfont = layout()->labelfont;
1362 tmpfont.setLanguage(getParLanguage(bparams));
1363 tmpfont.realize(outerfont);
1364 tmpfont.realize(bparams.getFont());
1369 Font const Paragraph::getLayoutFont
1370 (BufferParams const & bparams, Font const & outerfont) const
1372 Font tmpfont = layout()->font;
1373 tmpfont.setLanguage(getParLanguage(bparams));
1374 tmpfont.realize(outerfont);
1375 tmpfont.realize(bparams.getFont());
1380 /// Returns the height of the highest font in range
1381 Font_size Paragraph::highestFontInRange
1382 (pos_type startpos, pos_type endpos, Font_size def_size) const
1384 if (pimpl_->fontlist.empty())
1387 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1388 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1389 for (; end_it != end; ++end_it) {
1390 if (end_it->pos() >= endpos)
1397 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1398 for (; cit != end; ++cit) {
1399 if (cit->pos() >= startpos)
1403 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1404 for (; cit != end_it; ++cit) {
1405 Font::FONT_SIZE size = cit->font().size();
1406 if (size == Font::INHERIT_SIZE)
1408 if (size > maxsize && size <= Font::SIZE_HUGER)
1415 Paragraph::value_type
1416 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1418 value_type c = getChar(pos);
1419 if (!lyxrc.rtl_support)
1449 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1456 void Paragraph::setFont(pos_type pos, Font const & font)
1458 BOOST_ASSERT(pos <= size());
1460 // First, reduce font against layout/label font
1461 // Update: The setCharFont() routine in text2.cpp already
1462 // reduces font, so we don't need to do that here. (Asger)
1463 // No need to simplify this because it will disappear
1464 // in a new kernel. (Asger)
1465 // Next search font table
1467 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1468 Pimpl::FontList::iterator it = beg;
1469 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1470 for (; it != endit; ++it) {
1471 if (it->pos() >= pos)
1474 size_t const i = distance(beg, it);
1475 bool notfound = (it == endit);
1477 if (!notfound && pimpl_->fontlist[i].font() == font)
1480 bool begin = pos == 0 || notfound ||
1481 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1482 // Is position pos is a beginning of a font block?
1483 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1484 // Is position pos is the end of a font block?
1485 if (begin && end) { // A single char block
1486 if (i + 1 < pimpl_->fontlist.size() &&
1487 pimpl_->fontlist[i + 1].font() == font) {
1488 // Merge the singleton block with the next block
1489 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1490 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1491 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1492 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1493 // Merge the singleton block with the previous block
1494 pimpl_->fontlist[i - 1].pos(pos);
1495 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1497 pimpl_->fontlist[i].font(font);
1499 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1500 pimpl_->fontlist[i - 1].pos(pos);
1502 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1503 Pimpl::FontTable(pos, font));
1505 pimpl_->fontlist[i].pos(pos - 1);
1506 if (!(i + 1 < pimpl_->fontlist.size() &&
1507 pimpl_->fontlist[i + 1].font() == font))
1508 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1509 Pimpl::FontTable(pos, font));
1510 } else { // The general case. The block is splitted into 3 blocks
1511 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1512 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1513 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1514 Pimpl::FontTable(pos, font));
1519 void Paragraph::makeSameLayout(Paragraph const & par)
1521 layout(par.layout());
1523 params() = par.params();
1527 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1529 if (isFreeSpacing())
1535 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1536 if (eraseChar(pos, trackChanges))
1542 return count > 0 || pos > 0;
1546 bool Paragraph::hasSameLayout(Paragraph const & par) const
1548 return par.layout() == layout() && params().sameLayout(par.params());
1552 depth_type Paragraph::getDepth() const
1554 return params().depth();
1558 depth_type Paragraph::getMaxDepthAfter() const
1560 if (layout()->isEnvironment())
1561 return params().depth() + 1;
1563 return params().depth();
1567 char Paragraph::getAlign() const
1569 if (params().align() == LYX_ALIGN_LAYOUT)
1570 return layout()->align;
1572 return params().align();
1576 docstring const & Paragraph::getLabelstring() const
1578 return params().labelString();
1582 // the next two functions are for the manual labels
1583 docstring const Paragraph::getLabelWidthString() const
1585 if (!params().labelWidthString().empty())
1586 return params().labelWidthString();
1588 return _("Senseless with this layout!");
1592 void Paragraph::setLabelWidthString(docstring const & s)
1594 params().labelWidthString(s);
1598 docstring const Paragraph::translateIfPossible(docstring const & s,
1599 BufferParams const & bparams) const
1601 if (!support::isAscii(s) || s.empty()) {
1602 // This must be a user defined layout. We cannot translate
1603 // this, since gettext accepts only ascii keys.
1606 // Probably standard layout, try to translate
1607 Messages & m = getMessages(getParLanguage(bparams)->code());
1608 return m.get(to_ascii(s));
1612 docstring Paragraph::expandLabel(Layout_ptr const & layout,
1613 BufferParams const & bparams, bool process_appendix) const
1615 TextClass const & tclass = bparams.getTextClass();
1618 if (process_appendix && params().appendix())
1619 fmt = translateIfPossible(layout->labelstring_appendix(),
1622 fmt = translateIfPossible(layout->labelstring(), bparams);
1624 // handle 'inherited level parts' in 'fmt',
1625 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1626 size_t const i = fmt.find('@', 0);
1627 if (i != docstring::npos) {
1628 size_t const j = fmt.find('@', i + 1);
1629 if (j != docstring::npos) {
1630 docstring parent(fmt, i + 1, j - i - 1);
1632 docstring label = expandLabel(tclass[to_utf8(parent)], bparams);
1633 fmt = docstring(fmt, 0, i) + label + docstring(fmt, j + 1, docstring::npos);
1637 return tclass.counters().counterLabel(fmt);
1641 void Paragraph::applyLayout(Layout_ptr const & new_layout)
1644 params().labelWidthString(docstring());
1645 params().align(LYX_ALIGN_LAYOUT);
1646 params().spacing(Spacing(Spacing::Default));
1650 pos_type Paragraph::beginOfBody() const
1652 return begin_of_body_;
1656 void Paragraph::setBeginOfBody()
1658 if (layout()->labeltype != LABEL_MANUAL) {
1663 // Unroll the first two cycles of the loop
1664 // and remember the previous character to
1665 // remove unnecessary getChar() calls
1667 pos_type end = size();
1668 if (i < end && !isNewline(i)) {
1670 char_type previous_char = 0;
1673 previous_char = text_[i];
1674 if (!isNewline(i)) {
1676 while (i < end && previous_char != ' ') {
1681 previous_char = temp;
1691 // returns -1 if inset not found
1692 int Paragraph::getPositionOfInset(Inset const * inset) const
1695 InsetList::const_iterator it = insetlist.begin();
1696 InsetList::const_iterator end = insetlist.end();
1697 for (; it != end; ++it)
1698 if (it->inset == inset)
1704 InsetBibitem * Paragraph::bibitem() const
1706 if (!insetlist.empty()) {
1707 Inset * inset = insetlist.begin()->inset;
1708 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1709 return static_cast<InsetBibitem *>(inset);
1715 bool Paragraph::forceDefaultParagraphs() const
1717 return inInset() && inInset()->forceDefaultParagraphs(0);
1723 // paragraphs inside floats need different alignment tags to avoid
1726 bool noTrivlistCentering(Inset::Code code)
1728 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1732 string correction(string const & orig)
1734 if (orig == "flushleft")
1735 return "raggedright";
1736 if (orig == "flushright")
1737 return "raggedleft";
1738 if (orig == "center")
1744 string const corrected_env(string const & suffix, string const & env,
1747 string output = suffix + "{";
1748 if (noTrivlistCentering(code))
1749 output += correction(env);
1753 if (suffix == "\\begin")
1759 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1761 if (!contains(str, "\n"))
1762 column += str.size();
1766 column = rsplit(str, tmp, '\n').size();
1773 // This could go to ParagraphParameters if we want to
1774 int Paragraph::startTeXParParams(BufferParams const & bparams,
1775 odocstream & os, TexRow & texrow,
1776 bool moving_arg) const
1780 if (params().noindent()) {
1781 os << "\\noindent ";
1785 LyXAlignment const curAlign = params().align();
1787 if (curAlign == layout()->align)
1791 case LYX_ALIGN_NONE:
1792 case LYX_ALIGN_BLOCK:
1793 case LYX_ALIGN_LAYOUT:
1794 case LYX_ALIGN_SPECIAL:
1796 case LYX_ALIGN_LEFT:
1797 case LYX_ALIGN_RIGHT:
1798 case LYX_ALIGN_CENTER:
1807 case LYX_ALIGN_NONE:
1808 case LYX_ALIGN_BLOCK:
1809 case LYX_ALIGN_LAYOUT:
1810 case LYX_ALIGN_SPECIAL:
1812 case LYX_ALIGN_LEFT: {
1814 if (getParLanguage(bparams)->babel() != "hebrew")
1815 output = corrected_env("\\begin", "flushleft", ownerCode());
1817 output = corrected_env("\\begin", "flushright", ownerCode());
1818 os << from_ascii(output);
1819 adjust_row_column(output, texrow, column);
1821 } case LYX_ALIGN_RIGHT: {
1823 if (getParLanguage(bparams)->babel() != "hebrew")
1824 output = corrected_env("\\begin", "flushright", ownerCode());
1826 output = corrected_env("\\begin", "flushleft", ownerCode());
1827 os << from_ascii(output);
1828 adjust_row_column(output, texrow, column);
1830 } case LYX_ALIGN_CENTER: {
1832 output = corrected_env("\\begin", "center", ownerCode());
1833 os << from_ascii(output);
1834 adjust_row_column(output, texrow, column);
1843 // This could go to ParagraphParameters if we want to
1844 int Paragraph::endTeXParParams(BufferParams const & bparams,
1845 odocstream & os, TexRow & texrow,
1846 bool moving_arg) const
1850 switch (params().align()) {
1851 case LYX_ALIGN_NONE:
1852 case LYX_ALIGN_BLOCK:
1853 case LYX_ALIGN_LAYOUT:
1854 case LYX_ALIGN_SPECIAL:
1856 case LYX_ALIGN_LEFT:
1857 case LYX_ALIGN_RIGHT:
1858 case LYX_ALIGN_CENTER:
1866 switch (params().align()) {
1867 case LYX_ALIGN_NONE:
1868 case LYX_ALIGN_BLOCK:
1869 case LYX_ALIGN_LAYOUT:
1870 case LYX_ALIGN_SPECIAL:
1872 case LYX_ALIGN_LEFT: {
1874 if (getParLanguage(bparams)->babel() != "hebrew")
1875 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1877 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1878 os << from_ascii(output);
1879 adjust_row_column(output, texrow, column);
1881 } case LYX_ALIGN_RIGHT: {
1883 if (getParLanguage(bparams)->babel() != "hebrew")
1884 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1886 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1887 os << from_ascii(output);
1888 adjust_row_column(output, texrow, column);
1890 } case LYX_ALIGN_CENTER: {
1892 output = corrected_env("\n\\par\\end", "center", ownerCode());
1893 os << from_ascii(output);
1894 adjust_row_column(output, texrow, column);
1903 // This one spits out the text of the paragraph
1904 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1905 BufferParams const & bparams,
1906 Font const & outerfont,
1907 odocstream & os, TexRow & texrow,
1908 OutputParams const & runparams) const
1910 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1912 bool return_value = false;
1916 // well we have to check if we are in an inset with unlimited
1917 // length (all in one row) if that is true then we don't allow
1918 // any special options in the paragraph and also we don't allow
1919 // any environment other than the default layout of the text class
1921 bool asdefault = forceDefaultParagraphs();
1924 style = bparams.getTextClass().defaultLayout();
1929 // Current base font for all inherited font changes, without any
1930 // change caused by an individual character, except for the language:
1931 // It is set to the language of the first character.
1932 // As long as we are in the label, this font is the base font of the
1933 // label. Before the first body character it is set to the base font
1937 // Maybe we have to create a optional argument.
1938 pos_type body_pos = beginOfBody();
1939 unsigned int column = 0;
1942 // the optional argument is kept in curly brackets in
1943 // case it contains a ']'
1946 basefont = getLabelFont(bparams, outerfont);
1948 basefont = getLayoutFont(bparams, outerfont);
1951 // Which font is currently active?
1952 Font running_font(basefont);
1953 // Do we have an open font change?
1954 bool open_font = false;
1956 Change runningChange = Change(Change::UNCHANGED);
1958 texrow.start(id(), 0);
1960 // if the paragraph is empty, the loop will not be entered at all
1962 if (style->isCommand()) {
1967 column += startTeXParParams(bparams, os, texrow,
1968 runparams.moving_arg);
1971 for (pos_type i = 0; i < size(); ++i) {
1972 // First char in paragraph or after label?
1973 if (i == body_pos) {
1976 column += running_font.latexWriteEndChanges(
1977 os, bparams, runparams,
1978 basefont, basefont);
1981 basefont = getLayoutFont(bparams, outerfont);
1982 running_font = basefont;
1984 column += Changes::latexMarkChange(os, bparams,
1985 runningChange, Change(Change::UNCHANGED));
1986 runningChange = Change(Change::UNCHANGED);
1991 if (style->isCommand()) {
1997 column += startTeXParParams(bparams, os,
1999 runparams.moving_arg);
2002 Change const & change = pimpl_->lookupChange(i);
2004 if (bparams.outputChanges && runningChange != change) {
2006 column += running_font.latexWriteEndChanges(
2007 os, bparams, runparams, basefont, basefont);
2010 basefont = getLayoutFont(bparams, outerfont);
2011 running_font = basefont;
2013 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2014 runningChange = change;
2017 // do not output text which is marked deleted
2018 // if change tracking output is disabled
2019 if (!bparams.outputChanges && change.type == Change::DELETED) {
2025 value_type const c = getChar(i);
2027 // Fully instantiated font
2028 Font const font = getFont(bparams, i, outerfont);
2030 Font const last_font = running_font;
2032 // Do we need to close the previous font?
2034 (font != running_font ||
2035 font.language() != running_font.language()))
2037 column += running_font.latexWriteEndChanges(
2038 os, bparams, runparams, basefont,
2039 (i == body_pos-1) ? basefont : font);
2040 running_font = basefont;
2044 // Switch file encoding if necessary
2045 if (runparams.encoding->package() == Encoding::inputenc &&
2046 font.language()->encoding()->package() == Encoding::inputenc) {
2047 int const count = switchEncoding(os, bparams,
2048 runparams.moving_arg, *(runparams.encoding),
2049 *(font.language()->encoding()));
2052 runparams.encoding = font.language()->encoding();
2056 // Do we need to change font?
2057 if ((font != running_font ||
2058 font.language() != running_font.language()) &&
2061 column += font.latexWriteStartChanges(os, bparams,
2062 runparams, basefont,
2064 running_font = font;
2069 // Do not print the separation of the optional argument
2070 // if style->pass_thru is false. This works because
2071 // simpleTeXSpecialChars ignores spaces if
2072 // style->pass_thru is false.
2073 if (i != body_pos - 1) {
2074 if (pimpl_->simpleTeXBlanks(
2075 *(runparams.encoding), os, texrow,
2076 i, column, font, *style))
2077 // A surrogate pair was output. We
2078 // must not call simpleTeXSpecialChars
2079 // in this iteration, since
2080 // simpleTeXBlanks incremented i, and
2081 // simpleTeXSpecialChars would output
2082 // the combining character again.
2087 OutputParams rp = runparams;
2088 rp.free_spacing = style->free_spacing;
2089 rp.local_font = &font;
2090 rp.intitle = style->intitle;
2091 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2092 texrow, rp, running_font,
2093 basefont, outerfont, open_font,
2094 runningChange, *style, i, column, c);
2097 // If we have an open font definition, we have to close it
2099 #ifdef FIXED_LANGUAGE_END_DETECTION
2102 .latexWriteEndChanges(os, bparams, runparams,
2104 next_->getFont(bparams, 0, outerfont));
2106 running_font.latexWriteEndChanges(os, bparams,
2107 runparams, basefont, basefont);
2110 #ifdef WITH_WARNINGS
2111 //#warning For now we ALWAYS have to close the foreign font settings if they are
2112 //#warning there as we start another \selectlanguage with the next paragraph if
2113 //#warning we are in need of this. This should be fixed sometime (Jug)
2115 running_font.latexWriteEndChanges(os, bparams, runparams,
2116 basefont, basefont);
2120 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2122 // Needed if there is an optional argument but no contents.
2123 if (body_pos > 0 && body_pos == size()) {
2125 return_value = false;
2129 column += endTeXParParams(bparams, os, texrow,
2130 runparams.moving_arg);
2133 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2134 return return_value;
2151 string tag_name(PAR_TAG const & pt) {
2153 case PAR_NONE: return "!-- --";
2154 case TT: return "tt";
2155 case SF: return "sf";
2156 case BF: return "bf";
2157 case IT: return "it";
2158 case SL: return "sl";
2159 case EM: return "em";
2166 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2168 p1 = static_cast<PAR_TAG>(p1 | p2);
2173 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2175 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2181 bool Paragraph::emptyTag() const
2183 for (pos_type i = 0; i < size(); ++i) {
2185 Inset const * inset = getInset(i);
2186 Inset::Code lyx_code = inset->lyxCode();
2187 if (lyx_code != Inset::TOC_CODE &&
2188 lyx_code != Inset::INCLUDE_CODE &&
2189 lyx_code != Inset::GRAPHICS_CODE &&
2190 lyx_code != Inset::ERT_CODE &&
2191 lyx_code != Inset::LISTINGS_CODE &&
2192 lyx_code != Inset::FLOAT_CODE &&
2193 lyx_code != Inset::TABULAR_CODE) {
2197 value_type c = getChar(i);
2198 if (c != ' ' && c != '\t')
2206 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2208 for (pos_type i = 0; i < size(); ++i) {
2210 Inset const * inset = getInset(i);
2211 Inset::Code lyx_code = inset->lyxCode();
2212 if (lyx_code == Inset::LABEL_CODE) {
2213 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2214 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2223 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2226 for (i = 0; i < size(); ++i) {
2228 Inset const * inset = getInset(i);
2229 inset->docbook(buf, os, runparams);
2231 value_type c = getChar(i);
2234 os << sgml::escapeChar(c);
2241 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2245 for (pos_type i = initial; i < size(); ++i) {
2246 Font font = getFont(buf.params(), i, outerfont);
2249 if (i != initial && font != font_old)
2258 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2260 OutputParams const & runparams,
2261 Font const & outerfont,
2262 pos_type initial) const
2264 bool emph_flag = false;
2266 Layout_ptr const & style = layout();
2268 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2270 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2273 // parsing main loop
2274 for (pos_type i = initial; i < size(); ++i) {
2275 Font font = getFont(buf.params(), i, outerfont);
2277 // handle <emphasis> tag
2278 if (font_old.emph() != font.emph()) {
2279 if (font.emph() == Font::ON) {
2282 } else if (i != initial) {
2283 os << "</emphasis>";
2289 Inset const * inset = getInset(i);
2290 inset->docbook(buf, os, runparams);
2292 value_type c = getChar(i);
2294 if (style->pass_thru)
2297 os << sgml::escapeChar(c);
2303 os << "</emphasis>";
2306 if (style->free_spacing)
2308 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2313 bool Paragraph::isNewline(pos_type pos) const
2316 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2320 bool Paragraph::isLineSeparator(pos_type pos) const
2322 value_type const c = getChar(pos);
2323 return isLineSeparatorChar(c)
2324 || (c == Paragraph::META_INSET && getInset(pos) &&
2325 getInset(pos)->isLineSeparator());
2329 /// Used by the spellchecker
2330 bool Paragraph::isLetter(pos_type pos) const
2333 return getInset(pos)->isLetter();
2335 value_type const c = getChar(pos);
2336 return isLetterChar(c) || isDigit(c);
2342 Paragraph::getParLanguage(BufferParams const & bparams) const
2345 return getFirstFontSettings(bparams).language();
2346 #ifdef WITH_WARNINGS
2347 #warning FIXME we should check the prev par as well (Lgb)
2349 return bparams.language;
2353 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
2355 return lyxrc.rtl_support
2356 && getParLanguage(bparams)->rightToLeft()
2357 && ownerCode() != Inset::ERT_CODE
2358 && ownerCode() != Inset::LISTINGS_CODE;
2362 void Paragraph::changeLanguage(BufferParams const & bparams,
2363 Language const * from, Language const * to)
2365 // change language including dummy font change at the end
2366 for (pos_type i = 0; i <= size(); ++i) {
2367 Font font = getFontSettings(bparams, i);
2368 if (font.language() == from) {
2369 font.setLanguage(to);
2376 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2378 Language const * doc_language = bparams.language;
2379 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2380 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2382 for (; cit != end; ++cit)
2383 if (cit->font().language() != ignore_language &&
2384 cit->font().language() != latex_language &&
2385 cit->font().language() != doc_language)
2391 // Convert the paragraph to a string.
2392 // Used for building the table of contents
2393 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2395 return asString(buffer, 0, size(), label);
2399 docstring const Paragraph::asString(Buffer const & buffer,
2400 pos_type beg, pos_type end, bool label) const
2403 odocstringstream os;
2405 if (beg == 0 && label && !params().labelString().empty())
2406 os << params().labelString() << ' ';
2408 for (pos_type i = beg; i < end; ++i) {
2409 value_type const c = getChar(i);
2412 else if (c == META_INSET)
2413 getInset(i)->textString(buffer, os);
2420 void Paragraph::setInsetOwner(Inset * inset)
2422 pimpl_->inset_owner = inset;
2426 Change const & Paragraph::lookupChange(pos_type pos) const
2428 BOOST_ASSERT(pos <= size());
2429 return pimpl_->lookupChange(pos);
2433 bool Paragraph::isChanged(pos_type start, pos_type end) const
2435 return pimpl_->isChanged(start, end);
2439 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2441 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2445 void Paragraph::setChange(Change const & change)
2447 pimpl_->setChange(change);
2451 void Paragraph::setChange(pos_type pos, Change const & change)
2453 pimpl_->setChange(pos, change);
2457 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2459 return pimpl_->acceptChanges(bparams, start, end);
2463 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2465 return pimpl_->rejectChanges(bparams, start, end);
2469 int Paragraph::id() const
2475 Layout_ptr const & Paragraph::layout() const
2481 void Paragraph::layout(Layout_ptr const & new_layout)
2483 layout_ = new_layout;
2487 Inset * Paragraph::inInset() const
2489 return pimpl_->inset_owner;
2493 Inset::Code Paragraph::ownerCode() const
2495 return pimpl_->inset_owner
2496 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2500 ParagraphParameters & Paragraph::params()
2502 return pimpl_->params;
2506 ParagraphParameters const & Paragraph::params() const
2508 return pimpl_->params;
2512 bool Paragraph::isFreeSpacing() const
2514 if (layout()->free_spacing)
2517 // for now we just need this, later should we need this in some
2518 // other way we can always add a function to Inset too.
2519 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2523 bool Paragraph::allowEmpty() const
2525 if (layout()->keepempty)
2527 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2531 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2533 if (!Encodings::is_arabic(c))
2536 value_type prev_char = ' ';
2537 value_type next_char = ' ';
2539 for (pos_type i = pos - 1; i >= 0; --i) {
2540 value_type const par_char = getChar(i);
2541 if (!Encodings::isComposeChar_arabic(par_char)) {
2542 prev_char = par_char;
2547 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2548 value_type const par_char = getChar(i);
2549 if (!Encodings::isComposeChar_arabic(par_char)) {
2550 next_char = par_char;
2555 if (Encodings::is_arabic(next_char)) {
2556 if (Encodings::is_arabic(prev_char) &&
2557 !Encodings::is_arabic_special(prev_char))
2558 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2560 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2562 if (Encodings::is_arabic(prev_char) &&
2563 !Encodings::is_arabic_special(prev_char))
2564 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2566 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2571 bool Paragraph::hfillExpansion(Row const & row, pos_type pos) const
2576 BOOST_ASSERT(pos >= row.pos() && pos < row.endpos());
2578 // expand at the end of a row only if there is another hfill on the same row
2579 if (pos == row.endpos() - 1) {
2580 for (pos_type i = row.pos(); i < pos; i++) {
2587 // expand at the beginning of a row only if it is the first row of a paragraph
2588 if (pos == row.pos()) {
2592 // do not expand in some labels
2593 if (layout()->margintype != MARGIN_MANUAL && pos < beginOfBody())
2596 // if there is anything between the first char of the row and
2597 // the specified position that is neither a newline nor an hfill,
2598 // the hfill will be expanded, otherwise it won't
2599 for (pos_type i = row.pos(); i < pos; i++) {
2600 if (!isNewline(i) && !isHfill(i))
2607 int Paragraph::checkBiblio(bool track_changes)
2610 //This is getting more and more a mess. ...We really should clean
2611 //up this bibitem issue for 1.6. See also bug 2743.
2613 // Add bibitem insets if necessary
2614 if (layout()->labeltype != LABEL_BIBLIO)
2617 bool hasbibitem = !insetlist.empty()
2618 // Insist on it being in pos 0
2619 && getChar(0) == Paragraph::META_INSET
2620 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2625 // remove a bibitem in pos != 0
2626 // restore it later in pos 0 if necessary
2627 // (e.g. if a user inserts contents _before_ the item)
2628 // we're assuming there's only one of these, which there
2630 int erasedInsetPosition = -1;
2631 InsetList::iterator it = insetlist.begin();
2632 InsetList::iterator end = insetlist.end();
2633 for (; it != end; ++it)
2634 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2636 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2637 oldkey = olditem->getParam("key");
2638 oldlabel = olditem->getParam("label");
2639 erasedInsetPosition = it->pos;
2640 eraseChar(erasedInsetPosition, track_changes);
2644 //There was an InsetBibitem at the beginning, and we didn't
2645 //have to erase one.
2646 if (hasbibitem && erasedInsetPosition < 0)
2649 //There was an InsetBibitem at the beginning and we did have to
2650 //erase one. So we give its properties to the beginning inset.
2652 InsetBibitem * inset =
2653 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2654 if (!oldkey.empty())
2655 inset->setParam("key", oldkey);
2656 inset->setParam("label", oldlabel);
2657 return -erasedInsetPosition;
2660 //There was no inset at the beginning, so we need to create one with
2661 //the key and label of the one we erased.
2662 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2663 // restore values of previously deleted item in this par.
2664 if (!oldkey.empty())
2665 inset->setParam("key", oldkey);
2666 inset->setParam("label", oldlabel);
2667 insertInset(0, static_cast<Inset *>(inset),
2668 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));