]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
5be1cb3253a3e4462e23d0aac99dbaaacface34b
[lyx.git] / src / paragraph_pimpl.C
1 /**
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.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author John Levon
9  * \author André Pönitz
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "paragraph_pimpl.h"
17 #include "paragraph.h"
18
19 #include "bufferparams.h"
20 #include "debug.h"
21 #include "encoding.h"
22 #include "language.h"
23 #include "LaTeXFeatures.h"
24 #include "LColor.h"
25 #include "lyxlength.h"
26 #include "lyxrc.h"
27 #include "outputparams.h"
28 #include "texrow.h"
29
30 #include <boost/next_prior.hpp>
31
32 using lyx::docstring;
33 using lyx::odocstream;
34 using lyx::pos_type;
35
36 using std::endl;
37 using std::upper_bound;
38 using std::lower_bound;
39 using std::string;
40
41
42 // Initialization of the counter for the paragraph id's,
43 unsigned int Paragraph::Pimpl::paragraph_id = 0;
44
45 namespace {
46
47 struct special_phrase {
48         string phrase;
49         docstring macro;
50         bool builtin;
51 };
52
53 special_phrase const special_phrases[] = {
54         { "LyX", lyx::from_ascii("\\LyX{}"), false },
55         { "TeX", lyx::from_ascii("\\TeX{}"), true },
56         { "LaTeX2e", lyx::from_ascii("\\LaTeXe{}"), true },
57         { "LaTeX", lyx::from_ascii("\\LaTeX{}"), true },
58 };
59
60 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
61
62 } // namespace anon
63
64
65 Paragraph::Pimpl::Pimpl(Paragraph * owner)
66         : owner_(owner)
67 {
68         inset_owner = 0;
69         id_ = paragraph_id++;
70 }
71
72
73 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
74         : params(p.params), owner_(owner)
75 {
76         inset_owner = p.inset_owner;
77         fontlist = p.fontlist;
78         id_ = paragraph_id++;
79
80         if (p.tracking())
81                 changes_.reset(new Changes(*p.changes_.get()));
82 }
83
84
85 void Paragraph::Pimpl::setContentsFromPar(Paragraph const & par)
86 {
87         owner_->text_ = par.text_;
88         if (par.pimpl_->tracking()) {
89                 changes_.reset(new Changes(*(par.pimpl_->changes_.get())));
90         }
91 }
92
93
94 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
95 {
96         // FIXME: change tracking (MG)
97         if (!tracking())
98                 return false;
99
100         return changes_->isChange(start, end);
101 }
102
103
104 void Paragraph::Pimpl::setChange(Change const & change)
105 {
106         // FIXME: change tracking (MG)
107         // changes_.set(change, 0, size());
108
109         if (change.type == Change::UNCHANGED) { // only for UNCHANGED ???
110                 for (pos_type i = 0; i < size(); ++i) {
111                         if (owner_->isInset(i)) {
112                                 // FIXME: change tracking (MG)
113                                 // owner_->getInset(i)->setChange(change);
114                         }
115                 }
116         }
117 }
118
119
120 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
121 {
122         if (!tracking())
123                 return;
124
125         changes_->set(change, pos);
126 }
127
128
129 Change const Paragraph::Pimpl::lookupChange(pos_type pos) const
130 {
131         if (!tracking())
132                 return Change(Change::UNCHANGED);
133
134         return changes_->lookup(pos);
135 }
136
137
138 void Paragraph::Pimpl::acceptChange(pos_type start, pos_type end)
139 {
140         if (!tracking())
141                 return;
142
143         if (!size()) {
144                 changes_.reset(new Changes(Change::UNCHANGED));
145                 return;
146         }
147
148         lyxerr[Debug::CHANGES] << "acceptchange" << endl;
149         pos_type i = start;
150
151         for (; i < end; ++i) {
152                 switch (lookupChange(i).type) {
153                         case Change::UNCHANGED:
154                                 break;
155
156                         case Change::INSERTED:
157                                 changes_->set(Change::UNCHANGED, i);
158                                 break;
159
160                         case Change::DELETED:
161                                 // Suppress access to nonexistent
162                                 // "end-of-paragraph char":
163                                 if (i < size()) {
164                                         erase(i);
165                                         --end;
166                                         --i;
167                                 }
168                                 break;
169                 }
170         }
171
172         lyxerr[Debug::CHANGES] << "endacceptchange" << endl;
173         changes_->reset(Change::UNCHANGED);
174 }
175
176
177 void Paragraph::Pimpl::rejectChange(pos_type start, pos_type end)
178 {
179         if (!tracking())
180                 return;
181
182         if (!size()) {
183                 changes_.reset(new Changes(Change::UNCHANGED));
184                 return;
185         }
186
187         pos_type i = start;
188
189         for (; i < end; ++i) {
190                 switch (lookupChange(i).type) {
191                         case Change::UNCHANGED:
192                                 break;
193
194                         case Change::INSERTED:
195                                 if (i < size()) {
196                                         erase(i);
197                                         --end;
198                                         --i;
199                                 }
200                                 break;
201
202                         case Change::DELETED:
203                                 changes_->set(Change::UNCHANGED, i);
204                                 // No real char at position size():
205                                 if (i < size() && owner_->isInset(i))
206                                         owner_->getInset(i)->markErased(false);
207                                 break;
208                 }
209         }
210         changes_->reset(Change::UNCHANGED);
211 }
212
213
214 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
215 {
216         return owner_->getChar(pos);
217 }
218
219
220 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
221 {
222         BOOST_ASSERT(pos <= size());
223
224         if (tracking()) {
225                 changes_->record(change, pos);
226         }
227
228         // This is actually very common when parsing buffers (and
229         // maybe inserting ascii text)
230         if (pos == size()) {
231                 // when appending characters, no need to update tables
232                 owner_->text_.push_back(c);
233                 return;
234         }
235
236         owner_->text_.insert(owner_->text_.begin() + pos, c);
237
238         // Update the font table.
239         FontTable search_font(pos, LyXFont());
240         for (FontList::iterator it = lower_bound(fontlist.begin(),
241                                                       fontlist.end(),
242                                                       search_font, matchFT());
243              it != fontlist.end(); ++it)
244         {
245                 it->pos(it->pos() + 1);
246         }
247
248         // Update the insets
249         owner_->insetlist.increasePosAfterPos(pos);
250 }
251
252
253 void Paragraph::Pimpl::insertInset(pos_type pos,
254                                    InsetBase * inset, Change const & change)
255 {
256         BOOST_ASSERT(inset);
257         BOOST_ASSERT(pos <= size());
258
259         insertChar(pos, META_INSET, change);
260         BOOST_ASSERT(owner_->text_[pos] == META_INSET);
261
262         // Add a new entry in the insetlist.
263         owner_->insetlist.insert(inset, pos);
264 }
265
266
267 void Paragraph::Pimpl::erase(pos_type pos)
268 {
269         // FIXME: change tracking (MG)
270         // do something like changes_->erase(i);
271         // in one of the next patches, the two erase functions 
272         // will be merged but I don't want to break too many things at the same time :-)
273
274         // if it is an inset, delete the inset entry
275         if (owner_->text_[pos] == Paragraph::META_INSET) {
276                 owner_->insetlist.erase(pos);
277         }
278
279         owner_->text_.erase(owner_->text_.begin() + pos);
280
281         // Erase entries in the tables.
282         FontTable search_font(pos, LyXFont());
283
284         FontList::iterator it =
285                 lower_bound(fontlist.begin(),
286                             fontlist.end(),
287                             search_font, matchFT());
288         if (it != fontlist.end() && it->pos() == pos &&
289             (pos == 0 ||
290              (it != fontlist.begin()
291               && boost::prior(it)->pos() == pos - 1))) {
292                 // If it is a multi-character font
293                 // entry, we just make it smaller
294                 // (see update below), otherwise we
295                 // should delete it.
296                 unsigned int const i = it - fontlist.begin();
297                 fontlist.erase(fontlist.begin() + i);
298                 it = fontlist.begin() + i;
299                 if (i > 0 && i < fontlist.size() &&
300                     fontlist[i - 1].font() == fontlist[i].font()) {
301                         fontlist.erase(fontlist.begin() + i - 1);
302                         it = fontlist.begin() + i - 1;
303                 }
304         }
305
306         // Update all other entries
307         FontList::iterator fend = fontlist.end();
308         for (; it != fend; ++it)
309                 it->pos(it->pos() - 1);
310
311         // Update the insetlist
312         owner_->insetlist.decreasePosAfterPos(pos);
313 }
314
315
316 bool Paragraph::Pimpl::erase(pos_type pos, bool trackChanges)
317 {
318         // FIXME: change tracking (MG)
319         BOOST_ASSERT(pos <= size());
320
321         if (tracking()) {
322                 Change::Type changetype(changes_->lookup(pos).type);
323
324                 // only allow the actual removal if it was /new/ text
325                 if (changetype != Change::INSERTED) {
326                         changes_->record(Change(Change::DELETED), pos);
327                         if (pos < size() && owner_->isInset(pos))
328                                 owner_->getInset(pos)->markErased(true);
329                         return false;
330                 }
331         }
332
333         // Don't physically access nonexistent end-of-paragraph char
334         if (pos < size()) {
335                 erase(pos);
336                 return true;
337         }
338
339         return false;
340 }
341
342
343 int Paragraph::Pimpl::erase(pos_type start, pos_type end, bool trackChanges)
344 {
345         pos_type i = start;
346         for (pos_type count = end - start; count; --count) {
347                 if (!erase(i, trackChanges))
348                         ++i;
349         }
350         return end - i;
351 }
352
353
354 void Paragraph::Pimpl::simpleTeXBlanks(odocstream & os, TexRow & texrow,
355                                        pos_type const i,
356                                        unsigned int & column,
357                                        LyXFont const & font,
358                                        LyXLayout const & style)
359 {
360         if (style.pass_thru)
361                 return;
362
363         if (column > lyxrc.ascii_linelen
364             && i
365             && getChar(i - 1) != ' '
366             && (i < size() - 1)
367             // same in FreeSpacing mode
368             && !owner_->isFreeSpacing()
369             // In typewriter mode, we want to avoid
370             // ! . ? : at the end of a line
371             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
372                  && (getChar(i - 1) == '.'
373                      || getChar(i - 1) == '?'
374                      || getChar(i - 1) == ':'
375                      || getChar(i - 1) == '!'))) {
376                 os << '\n';
377                 texrow.newline();
378                 texrow.start(owner_->id(), i + 1);
379                 column = 0;
380         } else if (style.free_spacing) {
381                 os << '~';
382         } else {
383                 os << ' ';
384         }
385 }
386
387
388 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
389 {
390         pos_type const len = str.length();
391
392         // is the paragraph large enough?
393         if (pos + len > size())
394                 return false;
395
396         // does the wanted text start at point?
397         for (string::size_type i = 0; i < str.length(); ++i) {
398                 if (str[i] != owner_->text_[pos + i])
399                         return false;
400         }
401
402         // is there a font change in middle of the word?
403         FontList::const_iterator cit = fontlist.begin();
404         FontList::const_iterator end = fontlist.end();
405         for (; cit != end; ++cit) {
406                 if (cit->pos() >= pos)
407                         break;
408         }
409         if (cit != end && pos + len - 1 > cit->pos())
410                 return false;
411
412         return true;
413 }
414
415
416 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
417                                              BufferParams const & bparams,
418                                              odocstream & os,
419                                              TexRow & texrow,
420                                              OutputParams const & runparams,
421                                              LyXFont & font,
422                                              LyXFont & running_font,
423                                              LyXFont & basefont,
424                                              LyXFont const & outerfont,
425                                              bool & open_font,
426                                              Change::Type & running_change,
427                                              LyXLayout const & style,
428                                              pos_type & i,
429                                              unsigned int & column,
430                                              value_type const c)
431 {
432         if (style.pass_thru) {
433                 if (c != Paragraph::META_INSET) {
434                         if (c != '\0')
435                                 os.put(c);
436                 } else
437                         owner_->getInset(i)->plaintext(buf, os, runparams);
438                 return;
439         }
440
441         // Two major modes:  LaTeX or plain
442         // Handle here those cases common to both modes
443         // and then split to handle the two modes separately.
444         switch (c) {
445         case Paragraph::META_INSET: {
446                 InsetBase * inset = owner_->getInset(i);
447
448                 // FIXME: remove this check
449                 if (!inset)
450                         break;
451
452                 // FIXME: move this to InsetNewline::latex
453                 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
454                         // newlines are handled differently here than
455                         // the default in simpleTeXSpecialChars().
456                         if (!style.newline_allowed) {
457                                 os << '\n';
458                         } else {
459                                 if (open_font) {
460                                         column += running_font.latexWriteEndChanges(os, basefont, basefont);
461                                         open_font = false;
462                                 }
463                                 basefont = owner_->getLayoutFont(bparams, outerfont);
464                                 running_font = basefont;
465
466                                 if (font.family() == LyXFont::TYPEWRITER_FAMILY)
467                                         os << '~';
468
469                                 if (runparams.moving_arg)
470                                         os << "\\protect ";
471
472                                 os << "\\\\\n";
473                         }
474                         texrow.newline();
475                         texrow.start(owner_->id(), i + 1);
476                         column = 0;
477                         break;
478                 }
479
480                 // output change tracking marks only if desired,
481                 // if dvipost is installed,
482                 // and with dvi/ps (other formats don't work)
483                 LaTeXFeatures features(buf, bparams, runparams);
484                 bool const output = bparams.outputChanges
485                         && runparams.flavor == OutputParams::LATEX
486                         && features.isAvailable("dvipost");
487
488                 if (inset->canTrackChanges()) {
489                         column += Changes::latexMarkChange(os, running_change,
490                                 Change::UNCHANGED, output);
491                         running_change = Change::UNCHANGED;
492                 }
493
494                 bool close = false;
495                 odocstream::pos_type const len = os.tellp();
496
497                 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
498                      || inset->lyxCode() == InsetBase::MATH_CODE
499                      || inset->lyxCode() == InsetBase::URL_CODE)
500                     && running_font.isRightToLeft()) {
501                         os << "\\L{";
502                         close = true;
503                 }
504
505 #ifdef WITH_WARNINGS
506 #warning Bug: we can have an empty font change here!
507 // if there has just been a font change, we are going to close it
508 // right now, which means stupid latex code like \textsf{}. AFAIK,
509 // this does not harm dvi output. A minor bug, thus (JMarc)
510 #endif
511                 // some insets cannot be inside a font change command
512                 if (open_font && inset->noFontChange()) {
513                         column +=running_font.
514                                 latexWriteEndChanges(os,
515                                                      basefont,
516                                                      basefont);
517                         open_font = false;
518                         basefont = owner_->getLayoutFont(bparams, outerfont);
519                         running_font = basefont;
520                 }
521
522                 int tmp = inset->latex(buf, os, runparams);
523
524                 if (close)
525                         os << '}';
526
527                 if (tmp) {
528                         for (int j = 0; j < tmp; ++j) {
529                                 texrow.newline();
530                         }
531                         texrow.start(owner_->id(), i + 1);
532                         column = 0;
533                 } else {
534                         column += os.tellp() - len;
535                 }
536         }
537         break;
538
539         default:
540                 // And now for the special cases within each mode
541
542                 switch (c) {
543                 case '\\':
544                         os << "\\textbackslash{}";
545                         column += 15;
546                         break;
547
548                 case '±': case '²': case '³':
549                 case '×': case '÷': case '¹':
550                 case '¬': case 'µ':
551                         if ((bparams.inputenc == "latin1" ||
552                              bparams.inputenc == "latin9") ||
553                             (bparams.inputenc == "auto" &&
554                              (font.language()->encoding()->latexName()
555                               == "latin1" ||
556                               font.language()->encoding()->latexName()
557                               == "latin9"))) {
558                                 os << "\\ensuremath{";
559                                 os.put(c);
560                                 os << '}';
561                                 column += 13;
562                         } else {
563                                 os.put(c);
564                         }
565                         break;
566
567                 case '|': case '<': case '>':
568                         // In T1 encoding, these characters exist
569                         if (lyxrc.fontenc == "T1") {
570                                 os.put(c);
571                                 //... but we should avoid ligatures
572                                 if ((c == '>' || c == '<')
573                                     && i <= size() - 2
574                                     && getChar(i + 1) == c) {
575                                         //os << "\\textcompwordmark{}";
576                                         // Jean-Marc, have a look at
577                                         // this. I think this works
578                                         // equally well:
579                                         os << "\\,{}";
580                                         // Lgb
581                                         column += 19;
582                                 }
583                                 break;
584                         }
585                         // Typewriter font also has them
586                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
587                                 os.put(c);
588                                 break;
589                         }
590                         // Otherwise, we use what LaTeX
591                         // provides us.
592                         switch (c) {
593                         case '<':
594                                 os << "\\textless{}";
595                                 column += 10;
596                                 break;
597                         case '>':
598                                 os << "\\textgreater{}";
599                                 column += 13;
600                                 break;
601                         case '|':
602                                 os << "\\textbar{}";
603                                 column += 9;
604                                 break;
605                         }
606                         break;
607
608                 case '-': // "--" in Typewriter mode -> "-{}-"
609                         if (i <= size() - 2
610                             && getChar(i + 1) == '-'
611                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
612                                 os << "-{}";
613                                 column += 2;
614                         } else {
615                                 os << '-';
616                         }
617                         break;
618
619                 case '\"':
620                         os << "\\char`\\\"{}";
621                         column += 9;
622                         break;
623
624                 case '£':
625                         if (bparams.inputenc == "default") {
626                                 os << "\\pounds{}";
627                                 column += 8;
628                         } else {
629                                 os.put(c);
630                         }
631                         break;
632
633                 case '$': case '&':
634                 case '%': case '#': case '{':
635                 case '}': case '_':
636                         os << '\\';
637                         os.put(c);
638                         column += 1;
639                         break;
640
641                 case '~':
642                         os << "\\textasciitilde{}";
643                         column += 16;
644                         break;
645
646                 case '^':
647                         os << "\\textasciicircum{}";
648                         column += 17;
649                         break;
650
651                 case '*': case '[':
652                         // avoid being mistaken for optional arguments
653                         os << '{';
654                         os.put(c);
655                         os << '}';
656                         column += 2;
657                         break;
658
659                 case ' ':
660                         // Blanks are printed before font switching.
661                         // Sure? I am not! (try nice-latex)
662                         // I am sure it's correct. LyX might be smarter
663                         // in the future, but for now, nothing wrong is
664                         // written. (Asger)
665                         break;
666
667                 default:
668
669                         // I assume this is hack treating typewriter as verbatim
670                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
671                                 if (c != '\0') {
672                                         os.put(c);
673                                 }
674                                 break;
675                         }
676
677                         // LyX, LaTeX etc.
678
679                         // FIXME: if we have "LaTeX" with a font
680                         // change in the middle (before the 'T', then
681                         // the "TeX" part is still special cased.
682                         // Really we should only operate this on
683                         // "words" for some definition of word
684
685                         size_t pnr = 0;
686
687                         for (; pnr < phrases_nr; ++pnr) {
688                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
689                                         os << special_phrases[pnr].macro;
690                                         i += special_phrases[pnr].phrase.length() - 1;
691                                         column += special_phrases[pnr].macro.length() - 1;
692                                         break;
693                                 }
694                         }
695
696                         if (pnr == phrases_nr && c != '\0') {
697                                 os.put(c);
698                         }
699                         break;
700                 }
701         }
702 }
703
704
705 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
706                                 LyXLayout const & layout) const
707 {
708         BufferParams const & bparams = features.bufferParams();
709
710         // check the params.
711         if (!params.spacing().isDefault())
712                 features.require("setspace");
713
714         // then the layouts
715         features.useLayout(layout.name());
716
717         // then the fonts
718         Language const * doc_language = bparams.language;
719
720         FontList::const_iterator fcit = fontlist.begin();
721         FontList::const_iterator fend = fontlist.end();
722         for (; fcit != fend; ++fcit) {
723                 if (fcit->font().noun() == LyXFont::ON) {
724                         lyxerr[Debug::LATEX] << "font.noun: "
725                                              << fcit->font().noun()
726                                              << endl;
727                         features.require("noun");
728                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
729                                              << fcit->font().stateText(0)
730                                              << endl;
731                 }
732                 switch (fcit->font().color()) {
733                 case LColor::none:
734                 case LColor::inherit:
735                 case LColor::ignore:
736                         // probably we should put here all interface colors used for
737                         // font displaying! For now I just add this ones I know of (Jug)
738                 case LColor::latex:
739                 case LColor::note:
740                         break;
741                 default:
742                         features.require("color");
743                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
744                                              << fcit->font().stateText(0)
745                                              << endl;
746                 }
747
748                 Language const * language = fcit->font().language();
749                 if (language->babel() != doc_language->babel() &&
750                     language != ignore_language &&
751                     language != latex_language)
752                 {
753                         features.useLanguage(language);
754                         lyxerr[Debug::LATEX] << "Found language "
755                                              << language->babel() << endl;
756                 }
757         }
758
759         if (!params.leftIndent().zero())
760                 features.require("ParagraphLeftIndent");
761
762         // then the insets
763         InsetList::const_iterator icit = owner_->insetlist.begin();
764         InsetList::const_iterator iend = owner_->insetlist.end();
765         for (; icit != iend; ++icit) {
766                 if (icit->inset) {
767                         icit->inset->validate(features);
768                         if (layout.needprotect &&
769                             icit->inset->lyxCode() == InsetBase::FOOT_CODE)
770                                 features.require("NeedLyXFootnoteCode");
771                 }
772         }
773
774         // then the contents
775         for (pos_type i = 0; i < size() ; ++i) {
776                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
777                         if (!special_phrases[pnr].builtin
778                             && isTextAt(special_phrases[pnr].phrase, i)) {
779                                 features.require(special_phrases[pnr].phrase);
780                                 break;
781                         }
782                 }
783         }
784 }