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"
35 #include "OutputParams.h"
36 #include "output_latex.h"
37 #include "paragraph_funcs.h"
38 #include "ParagraphParameters.h"
43 #include "frontends/alert.h"
44 #include "frontends/FontMetrics.h"
46 #include "insets/InsetBibitem.h"
47 #include "insets/InsetOptArg.h"
49 #include "support/lstrings.h"
50 #include "support/textutils.h"
51 #include "support/convert.h"
52 #include "support/unicode.h"
54 #include <boost/bind.hpp>
55 #include <boost/next_prior.hpp>
67 using support::contains;
68 using support::suffixIs;
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 &,
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 & 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 (lookupChange(i).type == Change::DELETED) {
728 if( ++runparams.inDeletedInset == 1)
729 runparams.changeOfDeletedInset = lookupChange(i);
732 if (inset->canTrackChanges()) {
733 column += Changes::latexMarkChange(os, bparams, running_change,
734 Change(Change::UNCHANGED));
735 running_change = Change(Change::UNCHANGED);
739 odocstream::pos_type const len = os.tellp();
741 if ((inset->lyxCode() == Inset::GRAPHICS_CODE
742 || inset->lyxCode() == Inset::MATH_CODE
743 || inset->lyxCode() == Inset::URL_CODE)
744 && running_font.isRightToLeft()) {
745 if (running_font.language()->lang() == "farsi")
752 // FIXME: Bug: we can have an empty font change here!
753 // if there has just been a font change, we are going to close it
754 // right now, which means stupid latex code like \textsf{}. AFAIK,
755 // this does not harm dvi output. A minor bug, thus (JMarc)
756 // Some insets cannot be inside a font change command.
757 // However, even such insets *can* be placed in \L or \R
758 // or their equivalents (for RTL language switches), so we don't
759 // close the language in those cases.
760 // ArabTeX, though, cannot handle this special behavior, it seems.
761 bool arabtex = basefont.language()->lang() == "arabic_arabtex" ||
762 running_font.language()->lang() == "arabic_arabtex";
763 if (open_font && inset->noFontChange()) {
764 bool closeLanguage = arabtex ||
765 basefont.isRightToLeft() == running_font.isRightToLeft();
766 unsigned int count = running_font.latexWriteEndChanges(
767 os, bparams, runparams,
768 basefont, basefont, closeLanguage);
770 // if any font properties were closed, update the running_font,
771 // making sure, however, to leave the language as it was
773 // FIXME: probably a better way to keep track of the old
774 // language, than copying the entire font?
775 Font const copy_font(running_font);
776 basefont = owner_->getLayoutFont(bparams, outerfont);
777 running_font = basefont;
779 running_font.setLanguage(copy_font.language());
780 // leave font open if language is still open
781 open_font = (running_font.language() == basefont.language());
783 runparams.local_font = &basefont;
787 int tmp = inset->latex(buf, os, runparams);
790 if (running_font.language()->lang() == "farsi")
797 for (int j = 0; j < tmp; ++j) {
800 texrow.start(owner_->id(), i + 1);
803 column += os.tellp() - len;
806 if (lookupChange(i).type == Change::DELETED) {
807 --runparams.inDeletedInset;
813 // And now for the special cases within each mode
817 os << "\\textbackslash{}";
821 case '|': case '<': case '>':
822 // In T1 encoding, these characters exist
823 if (lyxrc.fontenc == "T1") {
825 //... but we should avoid ligatures
826 if ((c == '>' || c == '<')
828 && getChar(i + 1) == c) {
829 //os << "\\textcompwordmark{}";
831 // Jean-Marc, have a look at
832 // this. I think this works
840 // Typewriter font also has them
841 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
845 // Otherwise, we use what LaTeX
849 os << "\\textless{}";
853 os << "\\textgreater{}";
863 case '-': // "--" in Typewriter mode -> "-{}-"
864 if (i <= size() - 2 &&
865 getChar(i + 1) == '-' &&
866 running_font.family() == Font::TYPEWRITER_FAMILY) {
875 os << "\\char`\\\"{}";
880 case '%': case '#': case '{':
888 os << "\\textasciitilde{}";
893 os << "\\textasciicircum{}";
898 // avoid being mistaken for optional arguments
906 // Blanks are printed before font switching.
907 // Sure? I am not! (try nice-latex)
908 // I am sure it's correct. LyX might be smarter
909 // in the future, but for now, nothing wrong is
915 // I assume this is hack treating typewriter as verbatim
916 // FIXME UNICODE: This can fail if c cannot be encoded
917 // in the current encoding.
918 if (running_font.family() == Font::TYPEWRITER_FAMILY) {
927 // FIXME: if we have "LaTeX" with a font
928 // change in the middle (before the 'T', then
929 // the "TeX" part is still special cased.
930 // Really we should only operate this on
931 // "words" for some definition of word
935 for (; pnr < phrases_nr; ++pnr) {
936 if (isTextAt(special_phrases[pnr].phrase, i)) {
937 os << special_phrases[pnr].macro;
938 i += special_phrases[pnr].phrase.length() - 1;
939 column += special_phrases[pnr].macro.length() - 1;
944 if (pnr == phrases_nr && c != '\0') {
945 Encoding const & encoding = *(runparams.encoding);
946 if (i < size() - 1) {
947 char_type next = getChar(i + 1);
948 if (Encodings::isCombiningChar(next)) {
949 column += latexSurrogatePair(os, c, next, encoding) - 1;
954 docstring const latex = encoding.latexChar(c);
955 if (latex.length() > 1 &&
956 latex[latex.length() - 1] != '}') {
957 // Prevent eating of a following
958 // space or command corruption by
959 // following characters
960 column += latex.length() + 1;
963 column += latex.length() - 1;
973 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
974 Layout const & layout) const
976 BufferParams const & bparams = features.bufferParams();
979 if (!params.spacing().isDefault())
980 features.require("setspace");
983 features.useLayout(layout.name());
986 Language const * doc_language = bparams.language;
988 FontList::const_iterator fcit = fontlist.begin();
989 FontList::const_iterator fend = fontlist.end();
990 for (; fcit != fend; ++fcit) {
991 if (fcit->font().noun() == Font::ON) {
992 LYXERR(Debug::LATEX) << "font.noun: "
993 << fcit->font().noun()
995 features.require("noun");
996 LYXERR(Debug::LATEX) << "Noun enabled. Font: "
997 << to_utf8(fcit->font().stateText(0))
1000 switch (fcit->font().color()) {
1002 case Color::inherit:
1004 // probably we should put here all interface colors used for
1005 // font displaying! For now I just add this ones I know of (Jug)
1010 features.require("color");
1011 LYXERR(Debug::LATEX) << "Color enabled. Font: "
1012 << to_utf8(fcit->font().stateText(0))
1016 Language const * language = fcit->font().language();
1017 if (language->babel() != doc_language->babel() &&
1018 language != ignore_language &&
1019 language != latex_language)
1021 features.useLanguage(language);
1022 LYXERR(Debug::LATEX) << "Found language "
1023 << language->lang() << endl;
1027 if (!params.leftIndent().zero())
1028 features.require("ParagraphLeftIndent");
1031 InsetList::const_iterator icit = owner_->insetlist.begin();
1032 InsetList::const_iterator iend = owner_->insetlist.end();
1033 for (; icit != iend; ++icit) {
1035 icit->inset->validate(features);
1036 if (layout.needprotect &&
1037 icit->inset->lyxCode() == Inset::FOOT_CODE)
1038 features.require("NeedLyXFootnoteCode");
1042 // then the contents
1043 for (pos_type i = 0; i < size() ; ++i) {
1044 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
1045 if (!special_phrases[pnr].builtin
1046 && isTextAt(special_phrases[pnr].phrase, i)) {
1047 features.require(special_phrases[pnr].phrase);
1051 Encodings::validate(getChar(i), features);
1059 /////////////////////////////////////////////////////////////////////
1063 /////////////////////////////////////////////////////////////////////
1067 Paragraph::Paragraph()
1068 : begin_of_body_(0), pimpl_(new Paragraph::Pimpl(this))
1075 Paragraph::Paragraph(Paragraph const & par)
1076 : itemdepth(par.itemdepth), insetlist(par.insetlist),
1077 layout_(par.layout_),
1078 text_(par.text_), begin_of_body_(par.begin_of_body_),
1079 pimpl_(new Paragraph::Pimpl(*par.pimpl_, this))
1081 //lyxerr << "Paragraph::Paragraph(Paragraph const&)" << endl;
1082 InsetList::iterator it = insetlist.begin();
1083 InsetList::iterator end = insetlist.end();
1084 for (; it != end; ++it)
1085 it->inset = it->inset->clone();
1089 Paragraph & Paragraph::operator=(Paragraph const & par)
1091 // needed as we will destroy the pimpl_ before copying it
1093 itemdepth = par.itemdepth;
1095 insetlist = par.insetlist;
1096 InsetList::iterator it = insetlist.begin();
1097 InsetList::iterator end = insetlist.end();
1098 for (; it != end; ++it)
1099 it->inset = it->inset->clone();
1101 layout_ = par.layout();
1103 begin_of_body_ = par.begin_of_body_;
1106 pimpl_ = new Pimpl(*par.pimpl_, this);
1112 Paragraph::~Paragraph()
1116 //lyxerr << "Paragraph::paragraph_id = "
1117 // << Paragraph::paragraph_id << endl;
1121 void Paragraph::write(Buffer const & buf, ostream & os,
1122 BufferParams const & bparams,
1123 depth_type & dth) const
1125 // The beginning or end of a deeper (i.e. nested) area?
1126 if (dth != params().depth()) {
1127 if (params().depth() > dth) {
1128 while (params().depth() > dth) {
1129 os << "\n\\begin_deeper";
1133 while (params().depth() < dth) {
1134 os << "\n\\end_deeper";
1140 // First write the layout
1141 os << "\n\\begin_layout " << to_utf8(layout()->name()) << '\n';
1145 Font font1(Font::ALL_INHERIT, bparams.language);
1147 Change running_change = Change(Change::UNCHANGED);
1150 for (pos_type i = 0; i <= size(); ++i) {
1152 Change change = pimpl_->lookupChange(i);
1153 Changes::lyxMarkChange(os, column, running_change, change);
1154 running_change = change;
1159 // Write font changes
1160 Font font2 = getFontSettings(bparams, i);
1161 if (font2 != font1) {
1162 font2.lyxWriteChanges(font1, os);
1167 value_type const c = getChar(i);
1171 Inset const * inset = getInset(i);
1173 if (inset->directWrite()) {
1174 // international char, let it write
1175 // code directly so it's shorter in
1177 inset->write(buf, os);
1181 os << "\\begin_inset ";
1182 inset->write(buf, os);
1183 os << "\n\\end_inset\n\n";
1189 os << "\n\\backslash\n";
1193 if (i + 1 < size() && getChar(i + 1) == ' ') {
1200 if ((column > 70 && c == ' ')
1205 // this check is to amend a bug. LyX sometimes
1206 // inserts '\0' this could cause problems.
1208 std::vector<char> tmp = ucs4_to_utf8(c);
1209 tmp.push_back('\0');
1212 lyxerr << "ERROR (Paragraph::writeFile):"
1213 " NULL char in structure." << endl;
1219 os << "\n\\end_layout\n";
1223 void Paragraph::validate(LaTeXFeatures & features) const
1225 pimpl_->validate(features, *layout());
1229 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
1231 return pimpl_->eraseChar(pos, trackChanges);
1235 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
1237 return pimpl_->eraseChars(start, end, trackChanges);
1241 void Paragraph::insert(pos_type start, docstring const & str,
1242 Font const & font, Change const & change)
1244 for (size_t i = 0, n = str.size(); i != n ; ++i)
1245 insertChar(start + i, str[i], font, change);
1249 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1252 pimpl_->insertChar(pos, c, Change(trackChanges ?
1253 Change::INSERTED : Change::UNCHANGED));
1257 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1258 Font const & font, bool trackChanges)
1260 pimpl_->insertChar(pos, c, Change(trackChanges ?
1261 Change::INSERTED : Change::UNCHANGED));
1266 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
1267 Font const & font, Change const & change)
1269 pimpl_->insertChar(pos, c, change);
1274 void Paragraph::insertInset(pos_type pos, Inset * inset,
1275 Change const & change)
1277 pimpl_->insertInset(pos, inset, change);
1281 void Paragraph::insertInset(pos_type pos, Inset * inset,
1282 Font const & font, Change const & change)
1284 pimpl_->insertInset(pos, inset, change);
1285 // Set the font/language of the inset...
1290 bool Paragraph::insetAllowed(Inset_code code)
1292 return !pimpl_->inset_owner || pimpl_->inset_owner->insetAllowed(code);
1296 // Gets uninstantiated font setting at position.
1297 Font const Paragraph::getFontSettings(BufferParams const & bparams,
1301 lyxerr << " pos: " << pos << " size: " << size() << endl;
1302 BOOST_ASSERT(pos <= size());
1305 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1306 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1307 for (; cit != end; ++cit)
1308 if (cit->pos() >= pos)
1314 if (pos == size() && !empty())
1315 return getFontSettings(bparams, pos - 1);
1317 return Font(Font::ALL_INHERIT, getParLanguage(bparams));
1321 FontSpan Paragraph::fontSpan(pos_type pos) const
1323 BOOST_ASSERT(pos <= size());
1326 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1327 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1328 for (; cit != end; ++cit) {
1329 if (cit->pos() >= pos) {
1330 if (pos >= beginOfBody())
1331 return FontSpan(std::max(start, beginOfBody()),
1334 return FontSpan(start,
1335 std::min(beginOfBody() - 1,
1338 start = cit->pos() + 1;
1341 // This should not happen, but if so, we take no chances.
1342 //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
1344 return FontSpan(pos, pos);
1348 // Gets uninstantiated font setting at position 0
1349 Font const Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1351 if (!empty() && !pimpl_->fontlist.empty())
1352 return pimpl_->fontlist[0].font();
1354 return Font(Font::ALL_INHERIT, bparams.language);
1358 // Gets the fully instantiated font at a given position in a paragraph
1359 // This is basically the same function as Text::GetFont() in text2.cpp.
1360 // The difference is that this one is used for generating the LaTeX file,
1361 // and thus cosmetic "improvements" are disallowed: This has to deliver
1362 // the true picture of the buffer. (Asger)
1363 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1364 Font const & outerfont) const
1366 BOOST_ASSERT(pos >= 0);
1368 Font font = getFontSettings(bparams, pos);
1370 pos_type const body_pos = beginOfBody();
1372 font.realize(layout_->labelfont);
1374 font.realize(layout_->font);
1376 font.realize(outerfont);
1377 font.realize(bparams.getFont());
1383 Font const Paragraph::getLabelFont
1384 (BufferParams const & bparams, Font const & outerfont) const
1386 Font tmpfont = layout()->labelfont;
1387 tmpfont.setLanguage(getParLanguage(bparams));
1388 tmpfont.realize(outerfont);
1389 tmpfont.realize(bparams.getFont());
1394 Font const Paragraph::getLayoutFont
1395 (BufferParams const & bparams, Font const & outerfont) const
1397 Font tmpfont = layout()->font;
1398 tmpfont.setLanguage(getParLanguage(bparams));
1399 tmpfont.realize(outerfont);
1400 tmpfont.realize(bparams.getFont());
1405 /// Returns the height of the highest font in range
1406 Font_size Paragraph::highestFontInRange
1407 (pos_type startpos, pos_type endpos, Font_size def_size) const
1409 if (pimpl_->fontlist.empty())
1412 Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
1413 Pimpl::FontList::const_iterator const end = pimpl_->fontlist.end();
1414 for (; end_it != end; ++end_it) {
1415 if (end_it->pos() >= endpos)
1422 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1423 for (; cit != end; ++cit) {
1424 if (cit->pos() >= startpos)
1428 Font::FONT_SIZE maxsize = Font::SIZE_TINY;
1429 for (; cit != end_it; ++cit) {
1430 Font::FONT_SIZE size = cit->font().size();
1431 if (size == Font::INHERIT_SIZE)
1433 if (size > maxsize && size <= Font::SIZE_HUGER)
1440 Paragraph::value_type
1441 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
1443 value_type c = getChar(pos);
1444 if (!lyxrc.rtl_support)
1474 if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
1481 void Paragraph::setFont(pos_type pos, Font const & font)
1483 BOOST_ASSERT(pos <= size());
1485 // First, reduce font against layout/label font
1486 // Update: The setCharFont() routine in text2.cpp already
1487 // reduces font, so we don't need to do that here. (Asger)
1488 // No need to simplify this because it will disappear
1489 // in a new kernel. (Asger)
1490 // Next search font table
1492 Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
1493 Pimpl::FontList::iterator it = beg;
1494 Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
1495 for (; it != endit; ++it) {
1496 if (it->pos() >= pos)
1499 size_t const i = distance(beg, it);
1500 bool notfound = (it == endit);
1502 if (!notfound && pimpl_->fontlist[i].font() == font)
1505 bool begin = pos == 0 || notfound ||
1506 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
1507 // Is position pos is a beginning of a font block?
1508 bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
1509 // Is position pos is the end of a font block?
1510 if (begin && end) { // A single char block
1511 if (i + 1 < pimpl_->fontlist.size() &&
1512 pimpl_->fontlist[i + 1].font() == font) {
1513 // Merge the singleton block with the next block
1514 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1515 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1516 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
1517 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
1518 // Merge the singleton block with the previous block
1519 pimpl_->fontlist[i - 1].pos(pos);
1520 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
1522 pimpl_->fontlist[i].font(font);
1524 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
1525 pimpl_->fontlist[i - 1].pos(pos);
1527 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1528 Pimpl::FontTable(pos, font));
1530 pimpl_->fontlist[i].pos(pos - 1);
1531 if (!(i + 1 < pimpl_->fontlist.size() &&
1532 pimpl_->fontlist[i + 1].font() == font))
1533 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1534 Pimpl::FontTable(pos, font));
1535 } else { // The general case. The block is splitted into 3 blocks
1536 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
1537 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
1538 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
1539 Pimpl::FontTable(pos, font));
1544 void Paragraph::makeSameLayout(Paragraph const & par)
1546 layout(par.layout());
1548 params() = par.params();
1552 bool Paragraph::stripLeadingSpaces(bool trackChanges)
1554 if (isFreeSpacing())
1560 while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
1561 if (eraseChar(pos, trackChanges))
1567 return count > 0 || pos > 0;
1571 bool Paragraph::hasSameLayout(Paragraph const & par) const
1573 return par.layout() == layout() && params().sameLayout(par.params());
1577 depth_type Paragraph::getDepth() const
1579 return params().depth();
1583 depth_type Paragraph::getMaxDepthAfter() const
1585 if (layout()->isEnvironment())
1586 return params().depth() + 1;
1588 return params().depth();
1592 char Paragraph::getAlign() const
1594 if (params().align() == LYX_ALIGN_LAYOUT)
1595 return layout()->align;
1597 return params().align();
1601 docstring const & Paragraph::getLabelstring() const
1603 return params().labelString();
1607 // the next two functions are for the manual labels
1608 docstring const Paragraph::getLabelWidthString() const
1610 if (layout()->margintype == MARGIN_MANUAL)
1611 return params().labelWidthString();
1613 return _("Senseless with this layout!");
1617 void Paragraph::setLabelWidthString(docstring const & s)
1619 params().labelWidthString(s);
1623 docstring const Paragraph::translateIfPossible(docstring const & s,
1624 BufferParams const & bparams) const
1626 if (!support::isAscii(s) || s.empty()) {
1627 // This must be a user defined layout. We cannot translate
1628 // this, since gettext accepts only ascii keys.
1631 // Probably standard layout, try to translate
1632 Messages & m = getMessages(getParLanguage(bparams)->code());
1633 return m.get(to_ascii(s));
1637 docstring Paragraph::expandLabel(LayoutPtr const & layout,
1638 BufferParams const & bparams, bool process_appendix) const
1640 TextClass const & tclass = bparams.getTextClass();
1643 if (process_appendix && params().appendix())
1644 fmt = translateIfPossible(layout->labelstring_appendix(),
1647 fmt = translateIfPossible(layout->labelstring(), bparams);
1649 if (fmt.empty() && layout->labeltype == LABEL_COUNTER
1650 && !layout->counter.empty())
1651 fmt = "\\the" + layout->counter;
1653 // handle 'inherited level parts' in 'fmt',
1654 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
1655 size_t const i = fmt.find('@', 0);
1656 if (i != docstring::npos) {
1657 size_t const j = fmt.find('@', i + 1);
1658 if (j != docstring::npos) {
1659 docstring parent(fmt, i + 1, j - i - 1);
1660 docstring label = expandLabel(tclass[parent], bparams,
1662 fmt = docstring(fmt, 0, i) + label
1663 + docstring(fmt, j + 1, docstring::npos);
1667 return tclass.counters().counterLabel(fmt);
1671 void Paragraph::applyLayout(LayoutPtr const & new_layout)
1674 LyXAlignment const oldAlign = params().align();
1676 if (!(oldAlign & layout()->alignpossible)) {
1677 frontend::Alert::warning(_("Alignment not permitted"),
1678 _("The new layout does not permit the alignment previously used.\nSetting to default."));
1679 params().align(LYX_ALIGN_LAYOUT);
1684 pos_type Paragraph::beginOfBody() const
1686 return begin_of_body_;
1690 void Paragraph::setBeginOfBody()
1692 if (layout()->labeltype != LABEL_MANUAL) {
1697 // Unroll the first two cycles of the loop
1698 // and remember the previous character to
1699 // remove unnecessary getChar() calls
1701 pos_type end = size();
1702 if (i < end && !isNewline(i)) {
1704 char_type previous_char = 0;
1707 previous_char = text_[i];
1708 if (!isNewline(i)) {
1710 while (i < end && previous_char != ' ') {
1715 previous_char = temp;
1725 // returns -1 if inset not found
1726 int Paragraph::getPositionOfInset(Inset const * inset) const
1729 InsetList::const_iterator it = insetlist.begin();
1730 InsetList::const_iterator end = insetlist.end();
1731 for (; it != end; ++it)
1732 if (it->inset == inset)
1738 InsetBibitem * Paragraph::bibitem() const
1740 if (!insetlist.empty()) {
1741 Inset * inset = insetlist.begin()->inset;
1742 if (inset->lyxCode() == Inset::BIBITEM_CODE)
1743 return static_cast<InsetBibitem *>(inset);
1749 bool Paragraph::forceDefaultParagraphs() const
1751 return inInset() && inInset()->forceDefaultParagraphs(0);
1757 // paragraphs inside floats need different alignment tags to avoid
1760 bool noTrivlistCentering(Inset::Code code)
1762 return code == Inset::FLOAT_CODE || code == Inset::WRAP_CODE;
1766 string correction(string const & orig)
1768 if (orig == "flushleft")
1769 return "raggedright";
1770 if (orig == "flushright")
1771 return "raggedleft";
1772 if (orig == "center")
1778 string const corrected_env(string const & suffix, string const & env,
1781 string output = suffix + "{";
1782 if (noTrivlistCentering(code))
1783 output += correction(env);
1787 if (suffix == "\\begin")
1793 void adjust_row_column(string const & str, TexRow & texrow, int & column)
1795 if (!contains(str, "\n"))
1796 column += str.size();
1800 column = rsplit(str, tmp, '\n').size();
1807 // This could go to ParagraphParameters if we want to
1808 int Paragraph::startTeXParParams(BufferParams const & bparams,
1809 odocstream & os, TexRow & texrow,
1810 bool moving_arg) const
1814 if (params().noindent()) {
1815 os << "\\noindent ";
1819 LyXAlignment const curAlign = params().align();
1821 if (curAlign == layout()->align)
1825 case LYX_ALIGN_NONE:
1826 case LYX_ALIGN_BLOCK:
1827 case LYX_ALIGN_LAYOUT:
1828 case LYX_ALIGN_SPECIAL:
1830 case LYX_ALIGN_LEFT:
1831 case LYX_ALIGN_RIGHT:
1832 case LYX_ALIGN_CENTER:
1841 case LYX_ALIGN_NONE:
1842 case LYX_ALIGN_BLOCK:
1843 case LYX_ALIGN_LAYOUT:
1844 case LYX_ALIGN_SPECIAL:
1846 case LYX_ALIGN_LEFT: {
1848 if (getParLanguage(bparams)->babel() != "hebrew")
1849 output = corrected_env("\\begin", "flushleft", ownerCode());
1851 output = corrected_env("\\begin", "flushright", ownerCode());
1852 os << from_ascii(output);
1853 adjust_row_column(output, texrow, column);
1855 } case LYX_ALIGN_RIGHT: {
1857 if (getParLanguage(bparams)->babel() != "hebrew")
1858 output = corrected_env("\\begin", "flushright", ownerCode());
1860 output = corrected_env("\\begin", "flushleft", ownerCode());
1861 os << from_ascii(output);
1862 adjust_row_column(output, texrow, column);
1864 } case LYX_ALIGN_CENTER: {
1866 output = corrected_env("\\begin", "center", ownerCode());
1867 os << from_ascii(output);
1868 adjust_row_column(output, texrow, column);
1877 // This could go to ParagraphParameters if we want to
1878 int Paragraph::endTeXParParams(BufferParams const & bparams,
1879 odocstream & os, TexRow & texrow,
1880 bool moving_arg) const
1884 switch (params().align()) {
1885 case LYX_ALIGN_NONE:
1886 case LYX_ALIGN_BLOCK:
1887 case LYX_ALIGN_LAYOUT:
1888 case LYX_ALIGN_SPECIAL:
1890 case LYX_ALIGN_LEFT:
1891 case LYX_ALIGN_RIGHT:
1892 case LYX_ALIGN_CENTER:
1900 switch (params().align()) {
1901 case LYX_ALIGN_NONE:
1902 case LYX_ALIGN_BLOCK:
1903 case LYX_ALIGN_LAYOUT:
1904 case LYX_ALIGN_SPECIAL:
1906 case LYX_ALIGN_LEFT: {
1908 if (getParLanguage(bparams)->babel() != "hebrew")
1909 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1911 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1912 os << from_ascii(output);
1913 adjust_row_column(output, texrow, column);
1915 } case LYX_ALIGN_RIGHT: {
1917 if (getParLanguage(bparams)->babel() != "hebrew")
1918 output = corrected_env("\n\\par\\end", "flushright", ownerCode());
1920 output = corrected_env("\n\\par\\end", "flushleft", ownerCode());
1921 os << from_ascii(output);
1922 adjust_row_column(output, texrow, column);
1924 } case LYX_ALIGN_CENTER: {
1926 output = corrected_env("\n\\par\\end", "center", ownerCode());
1927 os << from_ascii(output);
1928 adjust_row_column(output, texrow, column);
1937 // This one spits out the text of the paragraph
1938 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
1939 BufferParams const & bparams,
1940 Font const & outerfont,
1941 odocstream & os, TexRow & texrow,
1942 OutputParams const & runparams) const
1944 LYXERR(Debug::LATEX) << "SimpleTeXOnePar... " << this << endl;
1946 bool return_value = false;
1950 // well we have to check if we are in an inset with unlimited
1951 // length (all in one row) if that is true then we don't allow
1952 // any special options in the paragraph and also we don't allow
1953 // any environment other than the default layout of the text class
1955 bool asdefault = forceDefaultParagraphs();
1958 style = bparams.getTextClass().defaultLayout();
1963 // Current base font for all inherited font changes, without any
1964 // change caused by an individual character, except for the language:
1965 // It is set to the language of the first character.
1966 // As long as we are in the label, this font is the base font of the
1967 // label. Before the first body character it is set to the base font
1971 // Maybe we have to create a optional argument.
1972 pos_type body_pos = beginOfBody();
1973 unsigned int column = 0;
1976 // the optional argument is kept in curly brackets in
1977 // case it contains a ']'
1980 basefont = getLabelFont(bparams, outerfont);
1982 basefont = getLayoutFont(bparams, outerfont);
1985 // Which font is currently active?
1986 Font running_font(basefont);
1987 // Do we have an open font change?
1988 bool open_font = false;
1990 Change runningChange = Change(Change::UNCHANGED);
1992 texrow.start(id(), 0);
1994 // if the paragraph is empty, the loop will not be entered at all
1996 if (style->isCommand()) {
2001 column += startTeXParParams(bparams, os, texrow,
2002 runparams.moving_arg);
2005 for (pos_type i = 0; i < size(); ++i) {
2006 // First char in paragraph or after label?
2007 if (i == body_pos) {
2010 column += running_font.latexWriteEndChanges(
2011 os, bparams, runparams,
2012 basefont, basefont);
2015 basefont = getLayoutFont(bparams, outerfont);
2016 running_font = basefont;
2018 column += Changes::latexMarkChange(os, bparams,
2019 runningChange, Change(Change::UNCHANGED));
2020 runningChange = Change(Change::UNCHANGED);
2025 if (style->isCommand()) {
2031 column += startTeXParParams(bparams, os,
2033 runparams.moving_arg);
2036 Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset
2037 : pimpl_->lookupChange(i);
2039 if (bparams.outputChanges && runningChange != change) {
2041 column += running_font.latexWriteEndChanges(
2042 os, bparams, runparams, basefont, basefont);
2045 basefont = getLayoutFont(bparams, outerfont);
2046 running_font = basefont;
2048 column += Changes::latexMarkChange(os, bparams, runningChange, change);
2049 runningChange = change;
2052 // do not output text which is marked deleted
2053 // if change tracking output is disabled
2054 if (!bparams.outputChanges && change.type == Change::DELETED) {
2060 value_type const c = getChar(i);
2062 // Fully instantiated font
2063 Font const font = getFont(bparams, i, outerfont);
2065 Font const last_font = running_font;
2067 // Do we need to close the previous font?
2069 (font != running_font ||
2070 font.language() != running_font.language()))
2072 column += running_font.latexWriteEndChanges(
2073 os, bparams, runparams, basefont,
2074 (i == body_pos-1) ? basefont : font);
2075 running_font = basefont;
2079 // Switch file encoding if necessary
2080 if (runparams.encoding->package() == Encoding::inputenc &&
2081 font.language()->encoding()->package() == Encoding::inputenc) {
2082 std::pair<bool, int> const enc_switch = switchEncoding(os, bparams,
2083 runparams.moving_arg, *(runparams.encoding),
2084 *(font.language()->encoding()));
2085 if (enc_switch.first) {
2086 column += enc_switch.second;
2087 runparams.encoding = font.language()->encoding();
2091 // Do we need to change font?
2092 if ((font != running_font ||
2093 font.language() != running_font.language()) &&
2096 odocstringstream ods;
2097 column += font.latexWriteStartChanges(ods, bparams,
2098 runparams, basefont,
2100 running_font = font;
2102 docstring fontchange = ods.str();
2103 // check if the fontchange ends with a trailing blank
2104 // (like "\small " (see bug 3382)
2105 if (suffixIs(fontchange, ' ') && c == ' ')
2106 os << fontchange.substr(0, fontchange.size() - 1)
2107 << from_ascii("{}");
2113 // Do not print the separation of the optional argument
2114 // if style->pass_thru is false. This works because
2115 // simpleTeXSpecialChars ignores spaces if
2116 // style->pass_thru is false.
2117 if (i != body_pos - 1) {
2118 if (pimpl_->simpleTeXBlanks(
2119 *(runparams.encoding), os, texrow,
2120 i, column, font, *style))
2121 // A surrogate pair was output. We
2122 // must not call simpleTeXSpecialChars
2123 // in this iteration, since
2124 // simpleTeXBlanks incremented i, and
2125 // simpleTeXSpecialChars would output
2126 // the combining character again.
2131 OutputParams rp = runparams;
2132 rp.free_spacing = style->free_spacing;
2133 rp.local_font = &font;
2134 rp.intitle = style->intitle;
2135 pimpl_->simpleTeXSpecialChars(buf, bparams, os,
2136 texrow, rp, running_font,
2137 basefont, outerfont, open_font,
2138 runningChange, *style, i, column, c);
2140 // Set the encoding to that returned from simpleTeXSpecialChars (see
2141 // comment for encoding member in OutputParams.h)
2142 runparams.encoding = rp.encoding;
2145 // If we have an open font definition, we have to close it
2147 #ifdef FIXED_LANGUAGE_END_DETECTION
2150 .latexWriteEndChanges(os, bparams, runparams,
2152 next_->getFont(bparams, 0, outerfont));
2154 running_font.latexWriteEndChanges(os, bparams,
2155 runparams, basefont, basefont);
2158 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
2159 //FIXME: there as we start another \selectlanguage with the next paragraph if
2160 //FIXME: we are in need of this. This should be fixed sometime (Jug)
2161 running_font.latexWriteEndChanges(os, bparams, runparams,
2162 basefont, basefont);
2166 column += Changes::latexMarkChange(os, bparams, runningChange, Change(Change::UNCHANGED));
2168 // Needed if there is an optional argument but no contents.
2169 if (body_pos > 0 && body_pos == size()) {
2171 return_value = false;
2175 column += endTeXParParams(bparams, os, texrow,
2176 runparams.moving_arg);
2179 LYXERR(Debug::LATEX) << "SimpleTeXOnePar...done " << this << endl;
2180 return return_value;
2197 string tag_name(PAR_TAG const & pt) {
2199 case PAR_NONE: return "!-- --";
2200 case TT: return "tt";
2201 case SF: return "sf";
2202 case BF: return "bf";
2203 case IT: return "it";
2204 case SL: return "sl";
2205 case EM: return "em";
2212 void operator|=(PAR_TAG & p1, PAR_TAG const & p2)
2214 p1 = static_cast<PAR_TAG>(p1 | p2);
2219 void reset(PAR_TAG & p1, PAR_TAG const & p2)
2221 p1 = static_cast<PAR_TAG>(p1 & ~p2);
2227 bool Paragraph::emptyTag() const
2229 for (pos_type i = 0; i < size(); ++i) {
2231 Inset const * inset = getInset(i);
2232 Inset::Code lyx_code = inset->lyxCode();
2233 if (lyx_code != Inset::TOC_CODE &&
2234 lyx_code != Inset::INCLUDE_CODE &&
2235 lyx_code != Inset::GRAPHICS_CODE &&
2236 lyx_code != Inset::ERT_CODE &&
2237 lyx_code != Inset::LISTINGS_CODE &&
2238 lyx_code != Inset::FLOAT_CODE &&
2239 lyx_code != Inset::TABULAR_CODE) {
2243 value_type c = getChar(i);
2244 if (c != ' ' && c != '\t')
2252 string Paragraph::getID(Buffer const & buf, OutputParams const & runparams) const
2254 for (pos_type i = 0; i < size(); ++i) {
2256 Inset const * inset = getInset(i);
2257 Inset::Code lyx_code = inset->lyxCode();
2258 if (lyx_code == Inset::LABEL_CODE) {
2259 string const id = static_cast<InsetCommand const *>(inset)->getContents();
2260 return "id='" + to_utf8(sgml::cleanID(buf, runparams, from_utf8(id))) + "'";
2269 pos_type Paragraph::getFirstWord(Buffer const & buf, odocstream & os, OutputParams const & runparams) const
2272 for (i = 0; i < size(); ++i) {
2274 Inset const * inset = getInset(i);
2275 inset->docbook(buf, os, runparams);
2277 value_type c = getChar(i);
2280 os << sgml::escapeChar(c);
2287 bool Paragraph::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
2291 for (pos_type i = initial; i < size(); ++i) {
2292 Font font = getFont(buf.params(), i, outerfont);
2295 if (i != initial && font != font_old)
2304 void Paragraph::simpleDocBookOnePar(Buffer const & buf,
2306 OutputParams const & runparams,
2307 Font const & outerfont,
2308 pos_type initial) const
2310 bool emph_flag = false;
2312 LayoutPtr const & style = layout();
2314 style->labeltype == LABEL_MANUAL ? style->labelfont : style->font;
2316 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2319 // parsing main loop
2320 for (pos_type i = initial; i < size(); ++i) {
2321 Font font = getFont(buf.params(), i, outerfont);
2323 // handle <emphasis> tag
2324 if (font_old.emph() != font.emph()) {
2325 if (font.emph() == Font::ON) {
2328 } else if (i != initial) {
2329 os << "</emphasis>";
2335 Inset const * inset = getInset(i);
2336 inset->docbook(buf, os, runparams);
2338 value_type c = getChar(i);
2340 if (style->pass_thru)
2343 os << sgml::escapeChar(c);
2349 os << "</emphasis>";
2352 if (style->free_spacing)
2354 if (style->pass_thru && !onlyText(buf, outerfont, initial))
2359 bool Paragraph::isNewline(pos_type pos) const
2362 && getInset(pos)->lyxCode() == Inset::NEWLINE_CODE;
2366 bool Paragraph::isLineSeparator(pos_type pos) const
2368 value_type const c = getChar(pos);
2369 return isLineSeparatorChar(c)
2370 || (c == Paragraph::META_INSET && getInset(pos) &&
2371 getInset(pos)->isLineSeparator());
2375 /// Used by the spellchecker
2376 bool Paragraph::isLetter(pos_type pos) const
2379 return getInset(pos)->isLetter();
2381 value_type const c = getChar(pos);
2382 return isLetterChar(c) || isDigit(c);
2388 Paragraph::getParLanguage(BufferParams const & bparams) const
2391 return getFirstFontSettings(bparams).language();
2392 // FIXME: we should check the prev par as well (Lgb)
2393 return bparams.language;
2397 bool Paragraph::isRTL(BufferParams const & bparams) const
2399 return lyxrc.rtl_support
2400 && getParLanguage(bparams)->rightToLeft()
2401 && ownerCode() != Inset::ERT_CODE
2402 && ownerCode() != Inset::LISTINGS_CODE;
2406 void Paragraph::changeLanguage(BufferParams const & bparams,
2407 Language const * from, Language const * to)
2409 // change language including dummy font change at the end
2410 for (pos_type i = 0; i <= size(); ++i) {
2411 Font font = getFontSettings(bparams, i);
2412 if (font.language() == from) {
2413 font.setLanguage(to);
2420 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
2422 Language const * doc_language = bparams.language;
2423 Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
2424 Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
2426 for (; cit != end; ++cit)
2427 if (cit->font().language() != ignore_language &&
2428 cit->font().language() != latex_language &&
2429 cit->font().language() != doc_language)
2435 // Convert the paragraph to a string.
2436 // Used for building the table of contents
2437 docstring const Paragraph::asString(Buffer const & buffer, bool label) const
2439 return asString(buffer, 0, size(), label);
2443 docstring const Paragraph::asString(Buffer const & buffer,
2444 pos_type beg, pos_type end, bool label) const
2447 odocstringstream os;
2449 if (beg == 0 && label && !params().labelString().empty())
2450 os << params().labelString() << ' ';
2452 for (pos_type i = beg; i < end; ++i) {
2453 value_type const c = getChar(i);
2456 else if (c == META_INSET)
2457 getInset(i)->textString(buffer, os);
2464 void Paragraph::setInsetOwner(Inset * inset)
2466 pimpl_->inset_owner = inset;
2470 Change const & Paragraph::lookupChange(pos_type pos) const
2472 BOOST_ASSERT(pos <= size());
2473 return pimpl_->lookupChange(pos);
2477 bool Paragraph::isChanged(pos_type start, pos_type end) const
2479 return pimpl_->isChanged(start, end);
2483 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
2485 return pimpl_->isMergedOnEndOfParDeletion(trackChanges);
2489 void Paragraph::setChange(Change const & change)
2491 pimpl_->setChange(change);
2495 void Paragraph::setChange(pos_type pos, Change const & change)
2497 pimpl_->setChange(pos, change);
2501 void Paragraph::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
2503 return pimpl_->acceptChanges(bparams, start, end);
2507 void Paragraph::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
2509 return pimpl_->rejectChanges(bparams, start, end);
2513 int Paragraph::id() const
2519 LayoutPtr const & Paragraph::layout() const
2525 void Paragraph::layout(LayoutPtr const & new_layout)
2527 layout_ = new_layout;
2531 Inset * Paragraph::inInset() const
2533 return pimpl_->inset_owner;
2537 Inset::Code Paragraph::ownerCode() const
2539 return pimpl_->inset_owner
2540 ? pimpl_->inset_owner->lyxCode() : Inset::NO_CODE;
2544 ParagraphParameters & Paragraph::params()
2546 return pimpl_->params;
2550 ParagraphParameters const & Paragraph::params() const
2552 return pimpl_->params;
2556 bool Paragraph::isFreeSpacing() const
2558 if (layout()->free_spacing)
2561 // for now we just need this, later should we need this in some
2562 // other way we can always add a function to Inset too.
2563 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2567 bool Paragraph::allowEmpty() const
2569 if (layout()->keepempty)
2571 return ownerCode() == Inset::ERT_CODE || ownerCode() == Inset::LISTINGS_CODE;
2575 char_type Paragraph::transformChar(char_type c, pos_type pos) const
2577 if (!Encodings::is_arabic(c))
2580 value_type prev_char = ' ';
2581 value_type next_char = ' ';
2583 for (pos_type i = pos - 1; i >= 0; --i) {
2584 value_type const par_char = getChar(i);
2585 if (!Encodings::isComposeChar_arabic(par_char)) {
2586 prev_char = par_char;
2591 for (pos_type i = pos + 1, end = size(); i < end; ++i) {
2592 value_type const par_char = getChar(i);
2593 if (!Encodings::isComposeChar_arabic(par_char)) {
2594 next_char = par_char;
2599 if (Encodings::is_arabic(next_char)) {
2600 if (Encodings::is_arabic(prev_char) &&
2601 !Encodings::is_arabic_special(prev_char))
2602 return Encodings::transformChar(c, Encodings::FORM_MEDIAL);
2604 return Encodings::transformChar(c, Encodings::FORM_INITIAL);
2606 if (Encodings::is_arabic(prev_char) &&
2607 !Encodings::is_arabic_special(prev_char))
2608 return Encodings::transformChar(c, Encodings::FORM_FINAL);
2610 return Encodings::transformChar(c, Encodings::FORM_ISOLATED);
2615 int Paragraph::checkBiblio(bool track_changes)
2618 //This is getting more and more a mess. ...We really should clean
2619 //up this bibitem issue for 1.6. See also bug 2743.
2621 // Add bibitem insets if necessary
2622 if (layout()->labeltype != LABEL_BIBLIO)
2625 bool hasbibitem = !insetlist.empty()
2626 // Insist on it being in pos 0
2627 && getChar(0) == Paragraph::META_INSET
2628 && insetlist.begin()->inset->lyxCode() == Inset::BIBITEM_CODE;
2633 // remove a bibitem in pos != 0
2634 // restore it later in pos 0 if necessary
2635 // (e.g. if a user inserts contents _before_ the item)
2636 // we're assuming there's only one of these, which there
2638 int erasedInsetPosition = -1;
2639 InsetList::iterator it = insetlist.begin();
2640 InsetList::iterator end = insetlist.end();
2641 for (; it != end; ++it)
2642 if (it->inset->lyxCode() == Inset::BIBITEM_CODE
2644 InsetBibitem * olditem = static_cast<InsetBibitem *>(it->inset);
2645 oldkey = olditem->getParam("key");
2646 oldlabel = olditem->getParam("label");
2647 erasedInsetPosition = it->pos;
2648 eraseChar(erasedInsetPosition, track_changes);
2652 //There was an InsetBibitem at the beginning, and we didn't
2653 //have to erase one.
2654 if (hasbibitem && erasedInsetPosition < 0)
2657 //There was an InsetBibitem at the beginning and we did have to
2658 //erase one. So we give its properties to the beginning inset.
2660 InsetBibitem * inset =
2661 static_cast<InsetBibitem *>(insetlist.begin()->inset);
2662 if (!oldkey.empty())
2663 inset->setParam("key", oldkey);
2664 inset->setParam("label", oldlabel);
2665 return -erasedInsetPosition;
2668 //There was no inset at the beginning, so we need to create one with
2669 //the key and label of the one we erased.
2670 InsetBibitem * inset(new InsetBibitem(InsetCommandParams("bibitem")));
2671 // restore values of previously deleted item in this par.
2672 if (!oldkey.empty())
2673 inset->setParam("key", oldkey);
2674 inset->setParam("label", oldlabel);
2675 insertInset(0, static_cast<Inset *>(inset),
2676 Change(track_changes ? Change::INSERTED : Change::UNCHANGED));
2682 void Paragraph::checkAuthors(AuthorList const & authorList)
2684 pimpl_->changes_.checkAuthors(authorList);