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