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