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