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