]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
another 8% or so when loading the UserGuide...
[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 "texrow.h"
19 #include "language.h"
20 #include "bufferparams.h"
21 #include "encoding.h"
22 #include "lyxrc.h"
23 #include "debug.h"
24 #include "lyxtextclasslist.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 extern int tex_code_break_column;
35
36
37 // Initialize static member.
38 ShareContainer<LyXFont> Paragraph::Pimpl::FontTable::container;
39 // Initialization of the counter for the paragraph id's,
40 unsigned int Paragraph::Pimpl::paragraph_id = 0;
41
42 namespace {
43
44 string special_phrases[][2] = {
45         { "LyX", "\\LyX{}" },
46         { "TeX", "\\TeX{}" },
47         { "LaTeX2e", "\\LaTeXe{}" },
48         { "LaTeX", "\\LaTeX{}" },
49         };
50
51 size_t phrases_nr = sizeof(special_phrases)/sizeof(special_phrases[0]);
52
53 } // namespace anon
54
55
56 Paragraph::Pimpl::Pimpl(Paragraph * owner)
57         : owner_(owner)
58 {
59         inset_owner = 0;
60         id_ = paragraph_id++;
61 }
62
63
64 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner, bool same_ids)
65         : params(p.params), owner_(owner)
66 {
67         inset_owner = p.inset_owner;
68         text = p.text;
69         fontlist = p.fontlist;
70         if (same_ids)
71                 id_ = p.id_;
72         else
73                 id_ = paragraph_id++;
74 }
75
76
77 void Paragraph::Pimpl::clear()
78 {
79         text.clear();
80 }
81
82
83 void Paragraph::Pimpl::setContentsFromPar(Paragraph const * par)
84 {
85         lyx::Assert(par);
86         text = par->pimpl_->text;
87 }
88
89
90 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
91 {
92         // This is in the critical path for loading!
93         pos_type siz = size();
94         lyx::Assert(pos <= siz);
95         // This is stronger, and I belive that this is the assertion
96         // that we should really use. (Lgb)
97         //Assert(pos < size());
98
99         // Then this has no meaning. (Lgb)
100         if (!siz || pos == siz)
101                 return '\0';
102
103         return text[pos];
104 }
105
106
107 void Paragraph::Pimpl::setChar(pos_type pos, value_type c)
108 {
109         text[pos] = c;
110 }
111
112
113 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c,
114                                   LyXFont const & font)
115 {
116         lyx::Assert(pos <= size());
117
118         // This is actually very common when parsing buffers (and
119         // maybe inserting ascii text)
120         if (pos == size()) {
121                 // when appending characters, no need to update tables
122                 text.push_back(c);
123                 owner_->setFont(pos, font);
124                 return;
125         }
126
127         text.insert(text.begin() + pos, c);
128
129         // Update the font table.
130         FontTable search_font(pos, LyXFont());
131         for (FontList::iterator it = lower_bound(fontlist.begin(),
132                                                       fontlist.end(),
133                                                       search_font, matchFT());
134              it != fontlist.end(); ++it)
135         {
136                 it->pos(it->pos() + 1);
137         }
138
139         // Update the inset table.
140         InsetTable search_inset(pos, 0);
141         for (InsetList::iterator it = lower_bound(owner_->insetlist.begin(),
142                                                        owner_->insetlist.end(),
143                                                        search_inset, matchIT());
144              it != owner_->insetlist.end(); ++it)
145         {
146                 ++it->pos;
147         }
148         owner_->setFont(pos, font);
149 }
150
151
152 void Paragraph::Pimpl::insertInset(pos_type pos,
153                                    Inset * inset, LyXFont const & font)
154 {
155         lyx::Assert(inset);
156         lyx::Assert(pos <= size());
157
158         insertChar(pos, META_INSET, font);
159         lyx::Assert(text[pos] == META_INSET);
160
161         // Add a new entry in the inset table.
162         InsetTable search_inset(pos, 0);
163         InsetList::iterator it = lower_bound(owner_->insetlist.begin(),
164                                                   owner_->insetlist.end(),
165                                                   search_inset, matchIT());
166         if (it != owner_->insetlist.end() && it->pos == pos) {
167                 lyxerr << "ERROR (Paragraph::InsertInset): "
168                         "there is an inset in position: " << pos << endl;
169         } else {
170                 owner_->insetlist.insert(it, InsetTable(pos, inset));
171                 inset->parOwner(owner_);
172         }
173
174         if (inset_owner)
175                 inset->setOwner(inset_owner);
176 }
177
178
179 void Paragraph::Pimpl::erase(pos_type pos)
180 {
181         lyx::Assert(pos < size());
182         // if it is an inset, delete the inset entry
183         if (text[pos] == Paragraph::META_INSET) {
184                 // find the entry
185                 InsetTable search_inset(pos, 0);
186                 InsetList::iterator it =
187                         lower_bound(owner_->insetlist.begin(),
188                                          owner_->insetlist.end(),
189                                          search_inset, matchIT());
190                 if (it != owner_->insetlist.end() && it->pos == pos) {
191                         delete it->inset;
192                         owner_->insetlist.erase(it);
193                 }
194         }
195
196         text.erase(text.begin() + pos);
197
198         // Erase entries in the tables.
199         FontTable search_font(pos, LyXFont());
200
201         FontList::iterator it =
202                 lower_bound(fontlist.begin(),
203                             fontlist.end(),
204                             search_font, matchFT());
205         if (it != fontlist.end() && it->pos() == pos &&
206             (pos == 0 ||
207              (it != fontlist.begin()
208               && boost::prior(it)->pos() == pos - 1))) {
209                 // If it is a multi-character font
210                 // entry, we just make it smaller
211                 // (see update below), otherwise we
212                 // should delete it.
213                 unsigned int const i = it - fontlist.begin();
214                 fontlist.erase(fontlist.begin() + i);
215                 it = fontlist.begin() + i;
216                 if (i > 0 && i < fontlist.size() &&
217                     fontlist[i - 1].font() == fontlist[i].font()) {
218                         fontlist.erase(fontlist.begin() + i - 1);
219                         it = fontlist.begin() + i - 1;
220                 }
221         }
222
223         // Update all other entries.
224         FontList::iterator fend = fontlist.end();
225         for (; it != fend; ++it)
226                 it->pos(it->pos() - 1);
227
228         // Update the inset table.
229         InsetTable search_inset(pos, 0);
230         InsetList::iterator lend = owner_->insetlist.end();
231         for (InsetList::iterator it =
232                      upper_bound(owner_->insetlist.begin(),
233                                       lend,
234                                       search_inset, matchIT());
235              it != lend; ++it)
236                 --it->pos;
237 }
238
239
240 void Paragraph::Pimpl::simpleTeXBlanks(ostream & os, TexRow & texrow,
241                                        pos_type const i,
242                                        int & column, LyXFont const & font,
243                                        LyXLayout const & style)
244 {
245         if (style.pass_thru) return;
246         if (column > tex_code_break_column
247             && i
248             && getChar(i - 1) != ' '
249             && (i < size() - 1)
250             // same in FreeSpacing mode
251             && !style.free_spacing
252                 && !owner_->isFreeSpacing()
253             // In typewriter mode, we want to avoid
254             // ! . ? : at the end of a line
255             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
256                  && (getChar(i - 1) == '.'
257                      || getChar(i - 1) == '?'
258                      || getChar(i - 1) == ':'
259                      || getChar(i - 1) == '!'))) {
260                 if (tex_code_break_column == 0) {
261                         // in batchmode we need LaTeX to still
262                         // see it as a space not as an extra '\n'
263                         os << " %\n";
264                 } else {
265                         os << '\n';
266                 }
267                 texrow.newline();
268                 texrow.start(owner_, i + 1);
269                 column = 0;
270         } else if (style.free_spacing) {
271                 os << '~';
272         } else {
273                 os << ' ';
274         }
275 }
276
277
278 bool Paragraph::Pimpl::isTextAt(BufferParams const & bp,
279                                 string const & str, pos_type pos)
280 {
281         LyXFont const & font = owner_->getFont(bp, pos);
282
283         for (string::size_type i = 0; i < str.length(); ++i) {
284                 if (pos + static_cast<pos_type>(i) >= size())
285                         return false;
286                 if (str[i] != getChar(pos + i))
287                         return false;
288                 if (owner_->getFont(bp, pos + i) != font)
289                         return false;
290         }
291         return true;
292 }
293
294
295 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const * buf,
296                                              BufferParams const & bparams,
297                                              ostream & os,
298                                              TexRow & texrow,
299                                              bool moving_arg,
300                                              LyXFont & font,
301                                              LyXFont & running_font,
302                                              LyXFont & basefont,
303                                              bool & open_font,
304                                              LyXLayout const & style,
305                                              pos_type & i,
306                                              int & column,
307                                              value_type const c)
308 {
309         if (style.pass_thru) {
310                 if (c != '\0') os << c;
311                 return;
312         }
313         // Two major modes:  LaTeX or plain
314         // Handle here those cases common to both modes
315         // and then split to handle the two modes separately.
316         switch (c) {
317         case Paragraph::META_INSET: {
318                 Inset * inset = owner_->getInset(i);
319                 if (inset) {
320                         bool close = false;
321                         int const len = os.tellp();
322                         //ostream::pos_type const len = os.tellp();
323                         if ((inset->lyxCode() == Inset::GRAPHICS_CODE
324                              || inset->lyxCode() == Inset::MATH_CODE
325                              || inset->lyxCode() == Inset::URL_CODE)
326                             && running_font.isRightToLeft()) {
327                                 os << "\\L{";
328                                 close = true;
329                         }
330
331                         int tmp = inset->latex(buf, os, moving_arg,
332                                                style.free_spacing);
333
334                         if (close)
335                                 os << "}";
336
337                         if (tmp) {
338                                 for (int j = 0; j < tmp; ++j) {
339                                         texrow.newline();
340                                 }
341                                 texrow.start(owner_, i + 1);
342                                 column = 0;
343                         } else {
344                                 column += int(os.tellp()) - len;
345                         }
346                 }
347         }
348         break;
349
350         case Paragraph::META_NEWLINE:
351                 if (open_font) {
352                         column += running_font.latexWriteEndChanges(os,
353                                                                     basefont,
354                                                                     basefont);
355                         open_font = false;
356                 }
357                 basefont = owner_->getLayoutFont(bparams);
358                 running_font = basefont;
359                 break;
360
361         case Paragraph::META_HFILL:
362                 os << "\\hfill{}";
363                 column += 7;
364                 break;
365
366         default:
367                 // And now for the special cases within each mode
368
369                 switch (c) {
370                 case '\\':
371                         os << "\\textbackslash{}";
372                         column += 15;
373                         break;
374
375                 case '±': case '²': case '³':
376                 case '×': case '÷': case '¹': 
377                 case '¬': case 'µ':
378                         if ((bparams.inputenc == "latin1" ||
379                              bparams.inputenc == "latin9") ||
380                             (bparams.inputenc == "auto" &&
381                              (font.language()->encoding()->LatexName()
382                               == "latin1" ||
383                               font.language()->encoding()->LatexName()
384                               == "latin9"))) {
385                                 os << "\\ensuremath{"
386                                    << c
387                                    << '}';
388                                 column += 13;
389                         } else {
390                                 os << c;
391                         }
392                         break;
393
394                 case '|': case '<': case '>':
395                         // In T1 encoding, these characters exist
396                         if (lyxrc.fontenc == "T1") {
397                                 os << c;
398                                 //... but we should avoid ligatures
399                                 if ((c == '>' || c == '<')
400                                     && i <= size() - 2
401                                     && getChar(i + 1) == c) {
402                                         //os << "\\textcompwordmark{}";
403                                         // Jean-Marc, have a look at
404                                         // this. I think this works
405                                         // equally well:
406                                         os << "\\,{}";
407                                         // Lgb
408                                         column += 19;
409                                 }
410                                 break;
411                         }
412                         // Typewriter font also has them
413                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
414                                 os << c;
415                                 break;
416                         }
417                         // Otherwise, we use what LaTeX
418                         // provides us.
419                         switch (c) {
420                         case '<':
421                                 os << "\\textless{}";
422                                 column += 10;
423                                 break;
424                         case '>':
425                                 os << "\\textgreater{}";
426                                 column += 13;
427                                 break;
428                         case '|':
429                                 os << "\\textbar{}";
430                                 column += 9;
431                                 break;
432                         }
433                         break;
434
435                 case '-': // "--" in Typewriter mode -> "-{}-"
436                         if (i <= size() - 2
437                             && getChar(i + 1) == '-'
438                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
439                                 os << "-{}";
440                                 column += 2;
441                         } else {
442                                 os << '-';
443                         }
444                         break;
445
446                 case '\"':
447                         os << "\\char`\\\"{}";
448                         column += 9;
449                         break;
450
451                 case '£':
452                         if (bparams.inputenc == "default") {
453                                 os << "\\pounds{}";
454                                 column += 8;
455                         } else {
456                                 os << c;
457                         }
458                         break;
459
460                 case '$': case '&':
461                 case '%': case '#': case '{':
462                 case '}': case '_':
463                         os << '\\' << c;
464                         column += 1;
465                         break;
466
467                 case '~':
468                         os << "\\textasciitilde{}";
469                         column += 16;
470                         break;
471
472                 case '^':
473                         os << "\\textasciicircum{}";
474                         column += 17;
475                         break;
476
477                 case '*': case '[': case ']':
478                         // avoid being mistaken for optional arguments
479                         os << '{' << c << '}';
480                         column += 2;
481                         break;
482
483                 case ' ':
484                         // Blanks are printed before font switching.
485                         // Sure? I am not! (try nice-latex)
486                         // I am sure it's correct. LyX might be smarter
487                         // in the future, but for now, nothing wrong is
488                         // written. (Asger)
489                         break;
490
491                 default:
492
493                         // I assume this is hack treating typewriter as verbatim
494                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
495                                 if (c != '\0') {
496                                         os << c;
497                                 }
498                                 break;
499                         }
500
501                         // LyX, LaTeX etc.
502
503                         // FIXME: if we have "LaTeX" with a font change in the middle (before
504                         // the 'T', then the "TeX" part is still special cased. Really we
505                         // should only operate this on "words" for some definition of word
506
507                         size_t pnr = 0;
508
509                         for (; pnr < phrases_nr; ++pnr) {
510                                 if (isTextAt(bparams, special_phrases[pnr][0], i)) {
511                                         os << special_phrases[pnr][1];
512                                         i += special_phrases[pnr][0].length() - 1;
513                                         column += special_phrases[pnr][1].length() - 1;
514                                         break;
515                                 }
516                         }
517
518                         if (pnr == phrases_nr && c != '\0') {
519                                 os << c;
520                         }
521                         break;
522                 }
523         }
524 }
525
526
527
528 Paragraph * Paragraph::Pimpl::TeXDeeper(Buffer const * buf,
529                                         BufferParams const & bparams,
530                                         ostream & os, TexRow & texrow)
531 {
532         lyxerr[Debug::LATEX] << "TeXDeeper...     " << this << endl;
533         Paragraph * par = owner_;
534
535         while (par && par->params().depth() == owner_->params().depth()) {
536                 if (textclasslist[bparams.textclass][par->layout()].isEnvironment()) {
537                         par = par->TeXEnvironment(buf, bparams,
538                                                   os, texrow);
539                 } else {
540                         par = par->TeXOnePar(buf, bparams,
541                                              os, texrow, false);
542                 }
543         }
544         lyxerr[Debug::LATEX] << "TeXDeeper...done " << par << endl;
545
546         return par;
547 }
548
549
550 Paragraph * Paragraph::Pimpl::getParFromID(int id) const
551 {
552         InsetList::const_iterator cit = owner_->insetlist.begin();
553         InsetList::const_iterator lend = owner_->insetlist.end();
554         Paragraph * result;
555         for (; cit != lend; ++cit) {
556                 if ((result = cit->inset->getParFromID(id)))
557                         return result;
558         }
559         return 0;
560 }
561
562
563 LyXFont const Paragraph::Pimpl::realizeFont(LyXFont const & font,
564                                             BufferParams const & bparams) const
565 {
566         LyXFont tmpfont(font);
567
568         // check for environment font information
569         char par_depth = owner_->getDepth();
570         Paragraph const * par = owner_;
571         LyXTextClass const & tclass = textclasslist[bparams.textclass];
572
573         while (par && par->getDepth() && !tmpfont.resolved()) {
574                 par = par->outerHook();
575                 if (par) {
576                         tmpfont.realize(tclass[par->layout()].font
577 #ifdef INHERIT_LANGUAGE
578                                         , bparams.language
579 #endif
580                                         );
581                         par_depth = par->getDepth();
582                 }
583         }
584
585         tmpfont.realize(tclass.defaultfont()
586 #ifdef INHERIT_LANGUAGE
587                 , bparams.language
588 #endif
589                 );
590         return tmpfont;
591 }