]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
fix dispatch calls as suggested by Herbert
[lyx.git] / src / paragraph_pimpl.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 1995 Matthias Ettrich
7  *           Copyright 1995-2001 The LyX Team.
8  *
9  * ====================================================== */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include "paragraph_pimpl.h"
18 #include "LaTeXFeatures.h"
19 #include "texrow.h"
20 #include "language.h"
21 #include "bufferparams.h"
22 #include "encoding.h"
23 #include "lyxrc.h"
24 #include "debug.h"
25 #include "lyxtextclasslist.h"
26
27 #include "support/LAssert.h"
28
29 using lyx::pos_type;
30 using std::endl;
31 using std::ostream;
32 using std::upper_bound;
33 using std::lower_bound;
34
35 extern int tex_code_break_column;
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, bool same_ids)
72         : params(p.params), owner_(owner)
73 {
74         inset_owner = p.inset_owner;
75         text = p.text;
76         fontlist = p.fontlist;
77         if (same_ids)
78                 id_ = p.id_;
79         else
80                 id_ = paragraph_id++;
81 }
82
83
84 void Paragraph::Pimpl::clear()
85 {
86         text.clear();
87 }
88
89
90 void Paragraph::Pimpl::setContentsFromPar(Paragraph const * par)
91 {
92         lyx::Assert(par);
93         text = par->pimpl_->text;
94 }
95
96
97 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
98 {
99         // This is in the critical path for loading!
100         pos_type const siz = size();
101         lyx::Assert(pos <= siz);
102         // This is stronger, and I belive that this is the assertion
103         // that we should really use. (Lgb)
104         //Assert(pos < size());
105
106         // Then this has no meaning. (Lgb)
107         if (!siz || pos == siz)
108                 return '\0';
109
110         return text[pos];
111 }
112
113
114 void Paragraph::Pimpl::setChar(pos_type pos, value_type c)
115 {
116         text[pos] = c;
117 }
118
119
120 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c,
121                                   LyXFont const & font)
122 {
123         lyx::Assert(pos <= size());
124
125         // This is actually very common when parsing buffers (and
126         // maybe inserting ascii text)
127         if (pos == size()) {
128                 // when appending characters, no need to update tables
129                 text.push_back(c);
130                 owner_->setFont(pos, font);
131                 return;
132         }
133
134         text.insert(text.begin() + pos, c);
135
136         // Update the font table.
137         FontTable search_font(pos, LyXFont());
138         for (FontList::iterator it = lower_bound(fontlist.begin(),
139                                                       fontlist.end(),
140                                                       search_font, matchFT());
141              it != fontlist.end(); ++it)
142         {
143                 it->pos(it->pos() + 1);
144         }
145
146         // Update the inset table.
147         InsetTable search_inset(pos, 0);
148         for (InsetList::iterator it = lower_bound(owner_->insetlist.begin(),
149                                                        owner_->insetlist.end(),
150                                                        search_inset, matchIT());
151              it != owner_->insetlist.end(); ++it)
152         {
153                 ++it->pos;
154         }
155         owner_->setFont(pos, font);
156 }
157
158
159 void Paragraph::Pimpl::insertInset(pos_type pos,
160                                    Inset * inset, LyXFont const & font)
161 {
162         lyx::Assert(inset);
163         lyx::Assert(pos <= size());
164
165         insertChar(pos, META_INSET, font);
166         lyx::Assert(text[pos] == META_INSET);
167
168         // Add a new entry in the inset table.
169         InsetTable search_inset(pos, 0);
170         InsetList::iterator it = lower_bound(owner_->insetlist.begin(),
171                                                   owner_->insetlist.end(),
172                                                   search_inset, matchIT());
173         if (it != owner_->insetlist.end() && it->pos == pos) {
174                 lyxerr << "ERROR (Paragraph::InsertInset): "
175                         "there is an inset in position: " << pos << endl;
176         } else {
177                 owner_->insetlist.insert(it, InsetTable(pos, inset));
178                 inset->parOwner(owner_);
179         }
180
181         if (inset_owner)
182                 inset->setOwner(inset_owner);
183 }
184
185
186 void Paragraph::Pimpl::erase(pos_type pos)
187 {
188         lyx::Assert(pos < size());
189         // if it is an inset, delete the inset entry
190         if (text[pos] == Paragraph::META_INSET) {
191                 // find the entry
192                 InsetTable search_inset(pos, 0);
193                 InsetList::iterator it =
194                         lower_bound(owner_->insetlist.begin(),
195                                          owner_->insetlist.end(),
196                                          search_inset, matchIT());
197                 if (it != owner_->insetlist.end() && it->pos == pos) {
198                         delete it->inset;
199                         owner_->insetlist.erase(it);
200                 }
201         }
202
203         text.erase(text.begin() + pos);
204
205         // Erase entries in the tables.
206         FontTable search_font(pos, LyXFont());
207
208         FontList::iterator it =
209                 lower_bound(fontlist.begin(),
210                             fontlist.end(),
211                             search_font, matchFT());
212         if (it != fontlist.end() && it->pos() == pos &&
213             (pos == 0 ||
214              (it != fontlist.begin()
215               && boost::prior(it)->pos() == pos - 1))) {
216                 // If it is a multi-character font
217                 // entry, we just make it smaller
218                 // (see update below), otherwise we
219                 // should delete it.
220                 unsigned int const i = it - fontlist.begin();
221                 fontlist.erase(fontlist.begin() + i);
222                 it = fontlist.begin() + i;
223                 if (i > 0 && i < fontlist.size() &&
224                     fontlist[i - 1].font() == fontlist[i].font()) {
225                         fontlist.erase(fontlist.begin() + i - 1);
226                         it = fontlist.begin() + i - 1;
227                 }
228         }
229
230         // Update all other entries.
231         FontList::iterator fend = fontlist.end();
232         for (; it != fend; ++it)
233                 it->pos(it->pos() - 1);
234
235         // Update the inset table.
236         InsetTable search_inset(pos, 0);
237         InsetList::iterator lend = owner_->insetlist.end();
238         for (InsetList::iterator it =
239                      upper_bound(owner_->insetlist.begin(),
240                                       lend,
241                                       search_inset, matchIT());
242              it != lend; ++it)
243                 --it->pos;
244 }
245
246
247 void Paragraph::Pimpl::simpleTeXBlanks(ostream & os, TexRow & texrow,
248                                        pos_type const i,
249                                        int & column, LyXFont const & font,
250                                        LyXLayout const & style)
251 {
252         if (style.pass_thru) return;
253         if (column > tex_code_break_column
254             && i
255             && getChar(i - 1) != ' '
256             && (i < size() - 1)
257             // same in FreeSpacing mode
258             && !style.free_spacing
259                 && !owner_->isFreeSpacing()
260             // In typewriter mode, we want to avoid
261             // ! . ? : at the end of a line
262             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
263                  && (getChar(i - 1) == '.'
264                      || getChar(i - 1) == '?'
265                      || getChar(i - 1) == ':'
266                      || getChar(i - 1) == '!'))) {
267                 if (tex_code_break_column == 0) {
268                         // in batchmode we need LaTeX to still
269                         // see it as a space not as an extra '\n'
270                         os << " %\n";
271                 } else {
272                         os << '\n';
273                 }
274                 texrow.newline();
275                 texrow.start(owner_, i + 1);
276                 column = 0;
277         } else if (style.free_spacing) {
278                 os << '~';
279         } else {
280                 os << ' ';
281         }
282 }
283
284
285 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
286 {
287         pos_type const len = str.length();
288
289         // is the paragraph large enough?
290         if (pos + len > size())
291                 return false;
292
293         // does the wanted text start at point?
294         for (string::size_type i = 0; i < str.length(); ++i) {
295                 if (str[i] != text[pos + i])
296                         return false;
297         }
298
299         // is there a font change in middle of the word?
300         FontList::const_iterator cit = fontlist.begin();
301         FontList::const_iterator end = fontlist.end();
302         for (; cit != end; ++cit) {
303                 if (cit->pos() >= pos)
304                         break;
305         }
306         if (cit != end && pos + len - 1 > cit->pos())
307                 return false;
308
309         return true;
310 }
311
312
313 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf,
314                                              BufferParams const & bparams,
315                                              ostream & os,
316                                              TexRow & texrow,
317                                              bool moving_arg,
318                                              LyXFont & font,
319                                              LyXFont & running_font,
320                                              LyXFont & basefont,
321                                              bool & open_font,
322                                              LyXLayout const & style,
323                                              pos_type & i,
324                                              int & column,
325                                              value_type const c)
326 {
327         if (style.pass_thru) {
328                 if (c != '\0') os << c;
329                 return;
330         }
331         // Two major modes:  LaTeX or plain
332         // Handle here those cases common to both modes
333         // and then split to handle the two modes separately.
334         switch (c) {
335         case Paragraph::META_INSET: {
336                 Inset * inset = owner_->getInset(i);
337                 if (inset) {
338                         bool close = false;
339                         int const len = os.tellp();
340                         //ostream::pos_type const len = os.tellp();
341                         if ((inset->lyxCode() == Inset::GRAPHICS_CODE
342                              || inset->lyxCode() == Inset::MATH_CODE
343                              || inset->lyxCode() == Inset::URL_CODE)
344                             && running_font.isRightToLeft()) {
345                                 os << "\\L{";
346                                 close = true;
347                         }
348
349                         int tmp = inset->latex(buf, os, moving_arg,
350                                                style.free_spacing);
351
352                         if (close)
353                                 os << "}";
354
355                         if (tmp) {
356                                 for (int j = 0; j < tmp; ++j) {
357                                         texrow.newline();
358                                 }
359                                 texrow.start(owner_, i + 1);
360                                 column = 0;
361                         } else {
362                                 column += int(os.tellp()) - len;
363                         }
364                 }
365         }
366         break;
367
368         case Paragraph::META_NEWLINE:
369                 if (open_font) {
370                         column += running_font.latexWriteEndChanges(os,
371                                                                     basefont,
372                                                                     basefont);
373                         open_font = false;
374                 }
375                 basefont = owner_->getLayoutFont(bparams);
376                 running_font = basefont;
377                 break;
378
379         case Paragraph::META_HFILL:
380                 os << "\\hfill{}";
381                 column += 7;
382                 break;
383
384         default:
385                 // And now for the special cases within each mode
386
387                 switch (c) {
388                 case '\\':
389                         os << "\\textbackslash{}";
390                         column += 15;
391                         break;
392
393                 case '±': case '²': case '³':
394                 case '×': case '÷': case '¹':
395                 case '¬': case 'µ':
396                         if ((bparams.inputenc == "latin1" ||
397                              bparams.inputenc == "latin9") ||
398                             (bparams.inputenc == "auto" &&
399                              (font.language()->encoding()->LatexName()
400                               == "latin1" ||
401                               font.language()->encoding()->LatexName()
402                               == "latin9"))) {
403                                 os << "\\ensuremath{"
404                                    << c
405                                    << '}';
406                                 column += 13;
407                         } else {
408                                 os << c;
409                         }
410                         break;
411
412                 case '|': case '<': case '>':
413                         // In T1 encoding, these characters exist
414                         if (lyxrc.fontenc == "T1") {
415                                 os << c;
416                                 //... but we should avoid ligatures
417                                 if ((c == '>' || c == '<')
418                                     && i <= size() - 2
419                                     && getChar(i + 1) == c) {
420                                         //os << "\\textcompwordmark{}";
421                                         // Jean-Marc, have a look at
422                                         // this. I think this works
423                                         // equally well:
424                                         os << "\\,{}";
425                                         // Lgb
426                                         column += 19;
427                                 }
428                                 break;
429                         }
430                         // Typewriter font also has them
431                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
432                                 os << c;
433                                 break;
434                         }
435                         // Otherwise, we use what LaTeX
436                         // provides us.
437                         switch (c) {
438                         case '<':
439                                 os << "\\textless{}";
440                                 column += 10;
441                                 break;
442                         case '>':
443                                 os << "\\textgreater{}";
444                                 column += 13;
445                                 break;
446                         case '|':
447                                 os << "\\textbar{}";
448                                 column += 9;
449                                 break;
450                         }
451                         break;
452
453                 case '-': // "--" in Typewriter mode -> "-{}-"
454                         if (i <= size() - 2
455                             && getChar(i + 1) == '-'
456                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
457                                 os << "-{}";
458                                 column += 2;
459                         } else {
460                                 os << '-';
461                         }
462                         break;
463
464                 case '\"':
465                         os << "\\char`\\\"{}";
466                         column += 9;
467                         break;
468
469                 case '£':
470                         if (bparams.inputenc == "default") {
471                                 os << "\\pounds{}";
472                                 column += 8;
473                         } else {
474                                 os << c;
475                         }
476                         break;
477
478                 case '$': case '&':
479                 case '%': case '#': case '{':
480                 case '}': case '_':
481                         os << '\\' << c;
482                         column += 1;
483                         break;
484
485                 case '~':
486                         os << "\\textasciitilde{}";
487                         column += 16;
488                         break;
489
490                 case '^':
491                         os << "\\textasciicircum{}";
492                         column += 17;
493                         break;
494
495                 case '*': case '[': case ']':
496                         // avoid being mistaken for optional arguments
497                         os << '{' << c << '}';
498                         column += 2;
499                         break;
500
501                 case ' ':
502                         // Blanks are printed before font switching.
503                         // Sure? I am not! (try nice-latex)
504                         // I am sure it's correct. LyX might be smarter
505                         // in the future, but for now, nothing wrong is
506                         // written. (Asger)
507                         break;
508
509                 default:
510
511                         // I assume this is hack treating typewriter as verbatim
512                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
513                                 if (c != '\0') {
514                                         os << c;
515                                 }
516                                 break;
517                         }
518
519                         // LyX, LaTeX etc.
520
521                         // FIXME: if we have "LaTeX" with a font
522                         // change in the middle (before the 'T', then
523                         // the "TeX" part is still special cased.
524                         // Really we should only operate this on
525                         // "words" for some definition of word
526
527                         size_t pnr = 0;
528
529                         for (; pnr < phrases_nr; ++pnr) {
530                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
531                                         os << special_phrases[pnr].macro;
532                                         i += special_phrases[pnr].phrase.length() - 1;
533                                         column += special_phrases[pnr].macro.length() - 1;
534                                         break;
535                                 }
536                         }
537
538                         if (pnr == phrases_nr && c != '\0') {
539                                 os << c;
540                         }
541                         break;
542                 }
543         }
544 }
545
546
547
548 Paragraph * Paragraph::Pimpl::TeXDeeper(Buffer const * buf,
549                                         BufferParams const & bparams,
550                                         ostream & os, TexRow & texrow)
551 {
552         lyxerr[Debug::LATEX] << "TeXDeeper...     " << this << endl;
553         Paragraph * par = owner_;
554
555         while (par && par->params().depth() == owner_->params().depth()) {
556                 if (par->layout()->isEnvironment()) {
557                         par = par->TeXEnvironment(buf, bparams,
558                                                   os, texrow);
559                 } else {
560                         par = par->TeXOnePar(buf, bparams,
561                                              os, texrow, false);
562                 }
563         }
564         lyxerr[Debug::LATEX] << "TeXDeeper...done " << par << endl;
565
566         return par;
567 }
568
569
570 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
571                                 LyXLayout const & layout) const
572 {
573         BufferParams const & bparams = features.bufferParams();
574
575         // check the params.
576         if (params.lineTop() || params.lineBottom())
577                 features.require("lyxline");
578         if (!params.spacing().isDefault())
579                 features.require("setspace");
580
581         // then the layouts
582         features.useLayout(layout.name());
583
584         // then the fonts
585         Language const * doc_language = bparams.language;
586
587         FontList::const_iterator fcit = fontlist.begin();
588         FontList::const_iterator fend = fontlist.end();
589         for (; fcit != fend; ++fcit) {
590                 if (fcit->font().noun() == LyXFont::ON) {
591                         lyxerr[Debug::LATEX] << "font.noun: "
592                                              << fcit->font().noun()
593                                              << endl;
594                         features.require("noun");
595                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
596                                              << fcit->font().stateText(0)
597                                              << endl;
598                 }
599                 switch (fcit->font().color()) {
600                 case LColor::none:
601                 case LColor::inherit:
602                 case LColor::ignore:
603                         // probably we should put here all interface colors used for
604                         // font displaying! For now I just add this ones I know of (Jug)
605                 case LColor::latex:
606                 case LColor::note:
607                         break;
608                 default:
609                         features.require("color");
610                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
611                                              << fcit->font().stateText(0)
612                                              << endl;
613                 }
614
615                 Language const * language = fcit->font().language();
616                 if (language->babel() != doc_language->babel() &&
617                     language != ignore_language &&
618 #ifdef INHERIT_LANGUAGE
619                     language != inherit_language &&
620 #endif
621                     language != latex_language)
622                 {
623                         features.useLanguage(language);
624                         lyxerr[Debug::LATEX] << "Found language "
625                                              << language->babel() << endl;
626                 }
627         }
628
629         if (!params.leftIndent().zero())
630                 features.require("ParagraphLeftIndent");
631
632         // then the insets
633         InsetList::const_iterator icit = owner_->insetlist.begin();
634         InsetList::const_iterator iend = owner_->insetlist.end();
635         for (; icit != iend; ++icit) {
636                 if (icit->inset) {
637                         icit->inset->validate(features);
638                         if (layout.needprotect &&
639                             icit->inset->lyxCode() == Inset::FOOT_CODE)
640                                 features.require("NeedLyXFootnoteCode");
641                 }
642         }
643
644         // then the contents
645         for (pos_type i = 0; i < size() ; ++i) {
646                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
647                         if (!special_phrases[pnr].builtin
648                             && isTextAt(special_phrases[pnr].phrase, i)) {
649                                 features.require(special_phrases[pnr].phrase);
650                                 break;
651                         }
652                 }
653         }               
654 }
655
656
657 Paragraph * Paragraph::Pimpl::getParFromID(int id) const
658 {
659         InsetList::const_iterator cit = owner_->insetlist.begin();
660         InsetList::const_iterator lend = owner_->insetlist.end();
661         Paragraph * result;
662         for (; cit != lend; ++cit) {
663                 if ((result = cit->inset->getParFromID(id)))
664                         return result;
665         }
666         return 0;
667 }
668
669
670 LyXFont const Paragraph::Pimpl::realizeFont(LyXFont const & font,
671                                             BufferParams const & bparams) const
672 {
673         LyXFont tmpfont(font);
674
675         // check for environment font information
676         char par_depth = owner_->getDepth();
677         Paragraph const * par = owner_;
678         LyXTextClass const & tclass = textclasslist[bparams.textclass];
679
680         while (par && par->getDepth() && !tmpfont.resolved()) {
681                 par = par->outerHook();
682                 if (par) {
683                         tmpfont.realize(par->layout()->font
684 #ifdef INHERIT_LANGUAGE
685                                         , bparams.language
686 #endif
687                                         );
688                         par_depth = par->getDepth();
689                 }
690         }
691
692         tmpfont.realize(tclass.defaultfont()
693 #ifdef INHERIT_LANGUAGE
694                 , bparams.language
695 #endif
696                 );
697         return tmpfont;
698 }