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