2 * \file paragraph_pimpl.C
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Jean-Marc Lasgouttes
11 * Full author contact details are available in file CREDITS.
16 #include "paragraph_pimpl.h"
17 #include "paragraph.h"
19 #include "bufferparams.h"
23 #include "LaTeXFeatures.h"
25 #include "lyxlength.h"
27 #include "outputparams.h"
30 #include <boost/next_prior.hpp>
36 using std::upper_bound;
37 using std::lower_bound;
41 // Initialization of the counter for the paragraph id's,
42 unsigned int Paragraph::Pimpl::paragraph_id = 0;
46 struct special_phrase {
52 special_phrase const special_phrases[] = {
53 { "LyX", from_ascii("\\LyX{}"), false },
54 { "TeX", from_ascii("\\TeX{}"), true },
55 { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
56 { "LaTeX", from_ascii("\\LaTeX{}"), true },
59 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
62 bool isEncoding(BufferParams const & bparams, LyXFont const & font,
63 string const & encoding)
65 return (bparams.inputenc == encoding
66 || (bparams.inputenc == "auto"
67 && font.language()->encoding()->latexName() == encoding));
73 Paragraph::Pimpl::Pimpl(Paragraph * owner)
81 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
82 : params(p.params), changes_(p.changes_), owner_(owner)
84 inset_owner = p.inset_owner;
85 fontlist = p.fontlist;
90 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
92 BOOST_ASSERT(start >= 0 && start <= size());
93 BOOST_ASSERT(end > start && end <= size() + 1);
95 return changes_.isChanged(start, end);
99 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
100 // keep the logic here in sync with the logic of eraseChars()
106 Change change = changes_.lookup(size());
108 return change.type == Change::INSERTED && change.author == 0;
112 void Paragraph::Pimpl::setChange(Change const & change)
114 // beware of the imaginary end-of-par character!
115 changes_.set(change, 0, size() + 1);
118 * Propagate the change recursively - but not in case of DELETED!
120 * Imagine that your co-author makes changes in an existing inset. He
121 * sends your document to you and you come to the conclusion that the
122 * inset should go completely. If you erase it, LyX must not delete all
123 * text within the inset. Otherwise, the change tracked insertions of
124 * your co-author get lost and there is no way to restore them later.
126 * Conclusion: An inset's content should remain untouched if you delete it
129 if (change.type != Change::DELETED) {
130 for (pos_type pos = 0; pos < size(); ++pos) {
131 if (owner_->isInset(pos)) {
132 owner_->getInset(pos)->setChange(change);
139 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
141 BOOST_ASSERT(pos >= 0 && pos <= size());
143 changes_.set(change, pos);
145 // see comment in setChange(Change const &) above
147 if (change.type != Change::DELETED &&
148 pos < size() && owner_->isInset(pos)) {
149 owner_->getInset(pos)->setChange(change);
154 Change const Paragraph::Pimpl::lookupChange(pos_type pos) const
156 BOOST_ASSERT(pos >= 0 && pos <= size());
158 return changes_.lookup(pos);
162 void Paragraph::Pimpl::acceptChanges(pos_type start, pos_type end)
164 BOOST_ASSERT(start >= 0 && start <= size());
165 BOOST_ASSERT(end > start && end <= size() + 1);
167 for (pos_type pos = start; pos < end; ++pos) {
168 switch (lookupChange(pos).type) {
169 case Change::UNCHANGED:
172 case Change::INSERTED:
173 changes_.set(Change(Change::UNCHANGED), pos);
176 case Change::DELETED:
177 // Suppress access to non-existent
178 // "end-of-paragraph char"
180 eraseChar(pos, false);
187 // also accept changes in nested insets
188 if (pos < size() && owner_->isInset(pos)) {
189 owner_->getInset(pos)->acceptChanges();
195 void Paragraph::Pimpl::rejectChanges(pos_type start, pos_type end)
197 BOOST_ASSERT(start >= 0 && start <= size());
198 BOOST_ASSERT(end > start && end <= size() + 1);
200 for (pos_type pos = start; pos < end; ++pos) {
201 switch (lookupChange(pos).type) {
202 case Change::UNCHANGED:
203 // also reject changes inside of insets
204 if (pos < size() && owner_->isInset(pos)) {
205 owner_->getInset(pos)->rejectChanges();
209 case Change::INSERTED:
210 // Suppress access to non-existent
211 // "end-of-paragraph char"
213 eraseChar(pos, false);
219 case Change::DELETED:
220 changes_.set(Change(Change::UNCHANGED), pos);
222 // Do NOT reject changes within a deleted inset!
223 // There may be insertions of a co-author inside of it!
231 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
233 BOOST_ASSERT(pos >= 0 && pos <= size());
235 return owner_->getChar(pos);
239 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
241 BOOST_ASSERT(pos >= 0 && pos <= size());
244 changes_.insert(change, pos);
246 // This is actually very common when parsing buffers (and
247 // maybe inserting ascii text)
249 // when appending characters, no need to update tables
250 owner_->text_.push_back(c);
254 owner_->text_.insert(owner_->text_.begin() + pos, c);
256 // Update the font table.
257 FontTable search_font(pos, LyXFont());
258 for (FontList::iterator it
259 = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
260 it != fontlist.end(); ++it)
262 it->pos(it->pos() + 1);
266 owner_->insetlist.increasePosAfterPos(pos);
270 void Paragraph::Pimpl::insertInset(pos_type pos, InsetBase * inset,
271 Change const & change)
274 BOOST_ASSERT(pos >= 0 && pos <= size());
276 insertChar(pos, META_INSET, change);
277 BOOST_ASSERT(owner_->text_[pos] == META_INSET);
279 // Add a new entry in the insetlist.
280 owner_->insetlist.insert(inset, pos);
284 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
286 BOOST_ASSERT(pos >= 0 && pos <= size());
288 // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
291 Change change = changes_.lookup(pos);
293 // set the character to DELETED if
294 // a) it was previously unchanged or
295 // b) it was inserted by a co-author
297 if (change.type == Change::UNCHANGED ||
298 (change.type == Change::INSERTED && change.author != 0)) {
299 setChange(pos, Change(Change::DELETED));
303 if (change.type == Change::DELETED)
307 // Don't physically access the imaginary end-of-paragraph character.
308 // eraseChar() can only mark it as DELETED. A physical deletion of
309 // end-of-par must be handled externally.
317 // if it is an inset, delete the inset entry
318 if (owner_->text_[pos] == Paragraph::META_INSET) {
319 owner_->insetlist.erase(pos);
322 owner_->text_.erase(owner_->text_.begin() + pos);
324 // Erase entries in the tables.
325 FontTable search_font(pos, LyXFont());
327 FontList::iterator it =
328 lower_bound(fontlist.begin(),
330 search_font, matchFT());
331 if (it != fontlist.end() && it->pos() == pos &&
333 (it != fontlist.begin()
334 && boost::prior(it)->pos() == pos - 1))) {
335 // If it is a multi-character font
336 // entry, we just make it smaller
337 // (see update below), otherwise we
339 unsigned int const i = it - fontlist.begin();
340 fontlist.erase(fontlist.begin() + i);
341 it = fontlist.begin() + i;
342 if (i > 0 && i < fontlist.size() &&
343 fontlist[i - 1].font() == fontlist[i].font()) {
344 fontlist.erase(fontlist.begin() + i - 1);
345 it = fontlist.begin() + i - 1;
349 // Update all other entries
350 FontList::iterator fend = fontlist.end();
351 for (; it != fend; ++it)
352 it->pos(it->pos() - 1);
354 // Update the insetlist
355 owner_->insetlist.decreasePosAfterPos(pos);
361 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
363 BOOST_ASSERT(start >= 0 && start <= size());
364 BOOST_ASSERT(end >= start && end <= size() + 1);
367 for (pos_type count = end - start; count; --count) {
368 if (!eraseChar(i, trackChanges))
375 void Paragraph::Pimpl::simpleTeXBlanks(odocstream & os, TexRow & texrow,
377 unsigned int & column,
378 LyXFont const & font,
379 LyXLayout const & style)
384 if (column > lyxrc.ascii_linelen
386 && getChar(i - 1) != ' '
388 // same in FreeSpacing mode
389 && !owner_->isFreeSpacing()
390 // In typewriter mode, we want to avoid
391 // ! . ? : at the end of a line
392 && !(font.family() == LyXFont::TYPEWRITER_FAMILY
393 && (getChar(i - 1) == '.'
394 || getChar(i - 1) == '?'
395 || getChar(i - 1) == ':'
396 || getChar(i - 1) == '!'))) {
399 texrow.start(owner_->id(), i + 1);
401 } else if (style.free_spacing) {
409 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
411 pos_type const len = str.length();
413 // is the paragraph large enough?
414 if (pos + len > size())
417 // does the wanted text start at point?
418 for (string::size_type i = 0; i < str.length(); ++i) {
419 if (str[i] != owner_->text_[pos + i])
423 // is there a font change in middle of the word?
424 FontList::const_iterator cit = fontlist.begin();
425 FontList::const_iterator end = fontlist.end();
426 for (; cit != end; ++cit) {
427 if (cit->pos() >= pos)
430 if (cit != end && pos + len - 1 > cit->pos())
437 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
438 BufferParams const & bparams,
441 OutputParams const & runparams,
443 LyXFont & running_font,
445 LyXFont const & outerfont,
447 Change::Type & running_change,
448 LyXLayout const & style,
450 unsigned int & column,
453 if (style.pass_thru) {
454 if (c != Paragraph::META_INSET) {
458 owner_->getInset(i)->plaintext(buf, os, runparams);
462 // Two major modes: LaTeX or plain
463 // Handle here those cases common to both modes
464 // and then split to handle the two modes separately.
466 case Paragraph::META_INSET: {
467 InsetBase * inset = owner_->getInset(i);
469 // FIXME: remove this check
473 // FIXME: move this to InsetNewline::latex
474 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
475 // newlines are handled differently here than
476 // the default in simpleTeXSpecialChars().
477 if (!style.newline_allowed) {
481 column += running_font.latexWriteEndChanges(os, basefont, basefont);
484 basefont = owner_->getLayoutFont(bparams, outerfont);
485 running_font = basefont;
487 if (font.family() == LyXFont::TYPEWRITER_FAMILY)
490 if (runparams.moving_arg)
496 texrow.start(owner_->id(), i + 1);
501 // output change tracking marks only if desired,
502 // if dvipost is installed,
503 // and with dvi/ps (other formats don't work)
504 LaTeXFeatures features(buf, bparams, runparams);
505 bool const output = bparams.outputChanges
506 && runparams.flavor == OutputParams::LATEX
507 && features.isAvailable("dvipost");
509 if (inset->canTrackChanges()) {
510 column += Changes::latexMarkChange(os, running_change,
511 Change::UNCHANGED, output);
512 running_change = Change::UNCHANGED;
516 odocstream::pos_type const len = os.tellp();
518 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
519 || inset->lyxCode() == InsetBase::MATH_CODE
520 || inset->lyxCode() == InsetBase::URL_CODE)
521 && running_font.isRightToLeft()) {
527 #warning Bug: we can have an empty font change here!
528 // if there has just been a font change, we are going to close it
529 // right now, which means stupid latex code like \textsf{}. AFAIK,
530 // this does not harm dvi output. A minor bug, thus (JMarc)
532 // some insets cannot be inside a font change command
533 if (open_font && inset->noFontChange()) {
534 column +=running_font.
535 latexWriteEndChanges(os,
539 basefont = owner_->getLayoutFont(bparams, outerfont);
540 running_font = basefont;
543 int tmp = inset->latex(buf, os, runparams);
549 for (int j = 0; j < tmp; ++j) {
552 texrow.start(owner_->id(), i + 1);
555 column += os.tellp() - len;
561 // And now for the special cases within each mode
565 os << "\\textbackslash{}";
569 // The following characters could be written literally in latin1, but they
570 // would be wrongly converted on systems where char is signed, so we give
572 // This also makes us independant from the encoding of this source file.
573 case 0xb1: // ± PLUS-MINUS SIGN
574 case 0xb2: // ² SUPERSCRIPT TWO
575 case 0xb3: // ³ SUPERSCRIPT THREE
576 case 0xd7: // × MULTIPLICATION SIGN
577 case 0xf7: // ÷ DIVISION SIGN
578 case 0xb9: // ¹ SUPERSCRIPT ONE
579 case 0xac: // ¬ NOT SIGN
580 case 0xb5: // µ MICRO SIGN
581 if (isEncoding(bparams, font, "latin1")
582 || isEncoding(bparams, font, "latin9")) {
583 os << "\\ensuremath{";
592 case '|': case '<': case '>':
593 // In T1 encoding, these characters exist
594 if (lyxrc.fontenc == "T1") {
596 //... but we should avoid ligatures
597 if ((c == '>' || c == '<')
599 && getChar(i + 1) == c) {
600 //os << "\\textcompwordmark{}";
601 // Jean-Marc, have a look at
602 // this. I think this works
610 // Typewriter font also has them
611 if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
615 // Otherwise, we use what LaTeX
619 os << "\\textless{}";
623 os << "\\textgreater{}";
633 case '-': // "--" in Typewriter mode -> "-{}-"
635 && getChar(i + 1) == '-'
636 && font.family() == LyXFont::TYPEWRITER_FAMILY) {
645 os << "\\char`\\\"{}";
649 case 0xa3: // £ POUND SIGN
650 if (bparams.inputenc == "default") {
658 case 0x20ac: // EURO SIGN
659 if (isEncoding(bparams, font, "latin9")
660 || isEncoding(bparams, font, "cp1251")
661 || isEncoding(bparams, font, "utf8")) {
664 os << "\\texteuro{}";
669 // These characters are covered by latin1, but not
670 // by latin9 (a.o.). We have to support them because
671 // we switched the default of latin1-languages to latin9
672 case 0xa4: // CURRENCY SYMBOL
673 case 0xa6: // BROKEN BAR
674 case 0xa8: // DIAERESIS
675 case 0xb4: // ACUTE ACCENT
676 case 0xb8: // CEDILLA
677 case 0xbd: // 1/2 FRACTION
678 case 0xbc: // 1/4 FRACTION
679 case 0xbe: // 3/4 FRACTION
680 if (isEncoding(bparams, font, "latin1")
681 || isEncoding(bparams, font, "latin5")
682 || isEncoding(bparams, font, "utf8")) {
688 os << "\\textcurrency{}";
692 os << "\\textbrokenbar{}";
696 os << "\\textasciidieresis{}";
700 os << "\\textasciiacute{}";
703 case 0xb8: // from latin1.def:
708 os << "\\textonehalf{}";
712 os << "\\textonequarter{}";
716 os << "\\textthreequarters{}";
724 case '%': case '#': case '{':
732 os << "\\textasciitilde{}";
737 os << "\\textasciicircum{}";
742 // avoid being mistaken for optional arguments
750 // Blanks are printed before font switching.
751 // Sure? I am not! (try nice-latex)
752 // I am sure it's correct. LyX might be smarter
753 // in the future, but for now, nothing wrong is
759 // I assume this is hack treating typewriter as verbatim
760 if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
769 // FIXME: if we have "LaTeX" with a font
770 // change in the middle (before the 'T', then
771 // the "TeX" part is still special cased.
772 // Really we should only operate this on
773 // "words" for some definition of word
777 for (; pnr < phrases_nr; ++pnr) {
778 if (isTextAt(special_phrases[pnr].phrase, i)) {
779 os << special_phrases[pnr].macro;
780 i += special_phrases[pnr].phrase.length() - 1;
781 column += special_phrases[pnr].macro.length() - 1;
786 if (pnr == phrases_nr && c != '\0') {
795 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
796 LyXLayout const & layout) const
798 BufferParams const & bparams = features.bufferParams();
801 if (!params.spacing().isDefault())
802 features.require("setspace");
805 features.useLayout(layout.name());
808 Language const * doc_language = bparams.language;
810 FontList::const_iterator fcit = fontlist.begin();
811 FontList::const_iterator fend = fontlist.end();
812 for (; fcit != fend; ++fcit) {
813 if (fcit->font().noun() == LyXFont::ON) {
814 lyxerr[Debug::LATEX] << "font.noun: "
815 << fcit->font().noun()
817 features.require("noun");
818 lyxerr[Debug::LATEX] << "Noun enabled. Font: "
819 << fcit->font().stateText(0)
822 switch (fcit->font().color()) {
824 case LColor::inherit:
826 // probably we should put here all interface colors used for
827 // font displaying! For now I just add this ones I know of (Jug)
832 features.require("color");
833 lyxerr[Debug::LATEX] << "Color enabled. Font: "
834 << fcit->font().stateText(0)
838 Language const * language = fcit->font().language();
839 if (language->babel() != doc_language->babel() &&
840 language != ignore_language &&
841 language != latex_language)
843 features.useLanguage(language);
844 lyxerr[Debug::LATEX] << "Found language "
845 << language->babel() << endl;
849 if (!params.leftIndent().zero())
850 features.require("ParagraphLeftIndent");
853 InsetList::const_iterator icit = owner_->insetlist.begin();
854 InsetList::const_iterator iend = owner_->insetlist.end();
855 for (; icit != iend; ++icit) {
857 icit->inset->validate(features);
858 if (layout.needprotect &&
859 icit->inset->lyxCode() == InsetBase::FOOT_CODE)
860 features.require("NeedLyXFootnoteCode");
865 for (pos_type i = 0; i < size() ; ++i) {
866 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
867 if (!special_phrases[pnr].builtin
868 && isTextAt(special_phrases[pnr].phrase, i)) {
869 features.require(special_phrases[pnr].phrase);
873 // these glyphs require the textcomp package
874 if (getChar(i) == 0x20ac || getChar(i) == 0xa4
875 || getChar(i) == 0xa6 || getChar(i) == 0xa8
876 || getChar(i) == 0xb4 || getChar(i) == 0xbd
877 || getChar(i) == 0xbc || getChar(i) == 0xbe)
878 features.require("textcomp");