]> git.lyx.org Git - lyx.git/blob - src/insets/insetlatexaccent.C
* src/LyXAction.C: mark goto-clear-bookmark as working without buffer
[lyx.git] / src / insets / insetlatexaccent.C
1 /**
2  * \file insetlatexaccent.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "insetlatexaccent.h"
14
15 #include "debug.h"
16 #include "language.h"
17 #include "LColor.h"
18 #include "lyxlex.h"
19 #include "lyxrc.h"
20 #include "metricsinfo.h"
21
22 #include "frontends/FontMetrics.h"
23 #include "frontends/Painter.h"
24
25 #include "support/lstrings.h"
26
27
28 namespace lyx {
29
30 using support::contains;
31 using support::trim;
32
33 using std::endl;
34 using std::string;
35 using std::auto_ptr;
36 using std::ostream;
37
38
39 /* LatexAccent. Proper handling of accented characters */
40 /* This part is done by Ivan Schreter, schreter@ccsun.tuke.sk */
41 /* Later modified by Lars G. Bjønnes, larsbj@lyx.org */
42
43 InsetLatexAccent::InsetLatexAccent()
44         : candisp(false)
45 {}
46
47
48 InsetLatexAccent::InsetLatexAccent(string const & str)
49         : contents(str)
50 {
51         checkContents();
52 }
53
54
55 auto_ptr<InsetBase> InsetLatexAccent::doClone() const
56 {
57         return auto_ptr<InsetBase>(new InsetLatexAccent(contents));
58 }
59
60
61 void InsetLatexAccent::checkContents()
62         // check, if we know the modifier and can display it ok on screen
63 {
64         candisp = false;
65
66         if (contents.empty() || contents.length() < 2) {
67                 lyxerr[Debug::KEY] << "Cannot decode: " << contents << endl;
68                 return;
69         }
70
71         contents = trim(contents);
72         if (contents[0] != '\\') { // demand that first char is a '\\'
73                 lyxerr[Debug::KEY] << "Cannot decode: " << contents << endl;
74                 return;
75         }
76
77         lyxerr[Debug::KEY] << "Decode: " << contents << endl;
78
79         remdot = false;
80         plusasc = false;
81         plusdesc = false;
82
83         switch (contents[1]) { // second char should be one of these
84         case '\'':  // acute
85                 modtype = ACUTE;    // acute
86                 plusasc = true;    // at the top of character
87                 break;
88         case '`':   // grave
89                 modtype = GRAVE;    // grave
90                 plusasc = true;    // at the top
91                 break;
92         case '=':   // macron
93                 modtype = MACRON;    // macron
94                 plusasc = true;    // at the top
95                 break;
96         case '~':   // tilde
97                 modtype = TILDE;    // tilde
98                 plusasc = true;    // at the top
99                 break;
100         case 'b':   // underbar
101                 modtype = UNDERBAR;    // underbar
102                 plusdesc = true;   // at the bottom
103                 break;
104         case 'c':   // cedilla
105                 modtype = CEDILLA;    // cedilla
106                 plusdesc = true;   // at the bottom
107                 break;
108         case 'd':   // underdot
109                 modtype = UNDERDOT;    // underdot
110                 plusdesc = true;   // at the bottom
111                 break;
112         case 'r':   // circle
113                 modtype = CIRCLE;    // circle
114                 plusasc = true;    // at the top
115                 break;
116         case 't':   // tie
117                 modtype = TIE;    // tie
118                 plusasc = true;    // at the top
119                 break;
120         case 'u':   // breve
121                 modtype = BREVE;    // breve
122                 plusasc = true;    // at the top
123                 break;
124         case 'v':   // caron
125                 modtype = CARON;   // caron
126                 plusasc = true;    // at the top
127                 break;
128         case 'q':   // special caron
129                 modtype = SPECIAL_CARON;   // special caron
130                 plusasc = true;    // at the top
131                 break;
132         case 'H':   // hungarian umlaut
133                 modtype = HUNGARIAN_UMLAUT;   // hungarian umlaut
134                 plusasc = true;    // at the top
135                 break;
136         case '"':   // umlaut
137                 modtype = UMLAUT;   // umlaut
138                 plusasc = true;    // at the top
139                 break;
140         case '.':   // dot
141                 modtype = DOT;   // dot
142                 plusasc = true;    // at the top
143                 break;
144         case '^':   // circumflex
145                 modtype = CIRCUMFLEX;   // circumflex
146                 plusasc = true;    // at the top
147                 break;
148         case 'k':   // ogonek
149                 modtype = OGONEK;  // ogonek
150                 plusdesc = true;
151                 break;
152         case 'i': // dot-less-i
153                 modtype = DOT_LESS_I;  // dot-less-i
154                 plusasc = true; // at the top (not really needed)
155                 remdot = true;
156                 break;
157         case 'j': // dot-less-j
158                 modtype = DOT_LESS_J; // dot-less-j
159                 plusasc = true; // at the top (not really needed)
160                 remdot = true;
161                 break;
162         case 'l': // lslash
163                 modtype = lSLASH;
164                 plusasc = true; // at the top (not really needed)
165                 break;
166         case 'L': // lslash
167                 modtype = LSLASH;
168                 plusasc = true; // at the top (not really needed)
169                 break;
170         default:
171                 lyxerr[Debug::KEY] << "Default" << endl;
172                 // unknown accent (or something else)
173                 return;
174         }
175
176         // we demand that third char is a '{' (Lgb)
177         if (contents[2] != '{') return;
178
179         // special clause for \i{}, \j{} \l{} and \L{}
180         if ((modtype == DOT_LESS_I || modtype == DOT_LESS_J
181              || modtype == lSLASH || modtype == LSLASH)
182             && contents[3] == '}') {
183                 switch (modtype) {
184                 case DOT_LESS_I: ic = 'i'; break;
185                 case DOT_LESS_J: ic = 'j'; break;
186                 case lSLASH:     ic = 'l'; break;
187                 case LSLASH:     ic = 'L'; break;
188                 default:
189                         // if this happens something is really wrong
190                         lyxerr << "InsetLaTexAccent: weird error." << endl;
191                         break;
192                 }
193                 //ic = (modtype == DOT_LESS_J ? 'j' : 'i');
194                 lyxerr[Debug::KEY] << "Contents: [" << contents << ']'
195                                    << ", ic: " << ic
196                                    << ", top: " << plusasc
197                                    << ", bot: " << plusdesc
198                                    << ", dot: " << remdot
199                                    << ", mod: " << modtype << endl;
200                 // Special case for space
201         } else if (contents[3] == '}') {
202                 ic = ' ';
203         } else {
204                 int i = 3;
205
206                 // now get the char
207                 ic = contents[3]; // i will always be 3 here
208
209                 // ic should now be a alfa-char or '\\'
210                 if (ic == '\\') {
211                         ic = contents[++i]; // will only allow \<foo>{\i} and \<foo>{\j}
212                         if (ic == 'i' || ic == 'j')
213                                 remdot = true;
214                         else
215                                 return;
216                 } else if ((ic == 'i'|| ic == 'j') && contents[4] == '}') {
217                         // Do a rewrite: \<foo>{i} --> \<foo>{\i}
218                         string temp = contents;
219                         temp.erase(3, string::npos);
220                         temp += '\\';
221                         temp += char(ic);
222                         for (string::size_type j = 4;
223                             j < contents.length(); ++j)
224                                 temp+= contents[j];
225                         contents= temp;
226                         ++i;
227                         remdot = true;
228                 }
229
230                 // demand a '}' at the end
231                 if (contents[++i] != '}' && contents[++i]) return;
232
233                 // fine, the char is properly decoded now (hopefully)
234                 lyxerr[Debug::KEY] << "Contents: [" << contents << ']'
235                                    << ", ic: " << ic
236                                    << ", top: " << plusasc
237                                    << ", bot: " << plusdesc
238                                    << ", dot: " << remdot
239                                    << ", mod: " << modtype << endl;
240         }
241         candisp = true;
242 }
243
244
245 bool InsetLatexAccent::metrics(MetricsInfo & mi, Dimension & dim) const
246 {
247         LyXFont & font = mi.base.font;
248         frontend::FontMetrics const & fm =      theFontMetrics(font);
249
250         // This function is a bit too simplistic and is just a
251         // "try to make a fit for all accents" approach, to
252         // make it better we need to know what kind of accent is
253         // used and add to max based on that.
254         if (candisp) {
255                 if (ic == ' ')
256                         dim.asc = fm.ascent('a');
257                 else
258                         dim.asc = fm.ascent(ic);
259                 if (plusasc)
260                         dim.asc += (fm.maxAscent() + 3) / 3;
261
262                 if (ic == ' ')
263                         dim.des = fm.descent('a');
264                 else
265                         dim.des = fm.descent(ic);
266                 if (plusdesc)
267                         dim.des += 3;
268
269                 dim.wid = fm.width(ic);
270         } else {
271                 dim.asc = fm.maxAscent() + 4;
272                 dim.des = fm.maxDescent() + 4;
273                 docstring dcon(contents.begin(), contents.end());
274                 dim.wid = fm.width(dcon) + 4;
275         }
276         bool const changed = dim_ != dim;
277         dim_ = dim;
278         return changed;
279 }
280
281
282 bool InsetLatexAccent::displayISO8859_9(PainterInfo & pi, int x, int y) const
283 {
284         unsigned char tmpic = ic;
285
286         switch (modtype) {
287
288         case CEDILLA: {
289                 if (ic == 'c') tmpic = '\xe7';
290                 if (ic == 'C') tmpic = '\xc7';
291                 if (ic == 's') tmpic = '\xfe';
292                 if (ic == 'S') tmpic = '\xde';
293                 break;
294         }
295
296         case BREVE: {
297                 if (ic == 'g') tmpic = '\xf0';
298                 if (ic == 'G') tmpic = '\xd0';
299                 break;
300         }
301
302         case UMLAUT: {
303                 if (ic == 'o') tmpic = '\xf6';
304                 if (ic == 'O') tmpic = '\xd6';
305                 if (ic == 'u') tmpic = '\xfc';
306                 if (ic == 'U') tmpic = '\xdc';
307                 break;
308         }
309
310         case DOT:
311                 if (ic == 'I') tmpic = '\xdd';
312                 break;
313
314         case DOT_LESS_I:
315                 tmpic = '\xfd';
316                 break;
317
318         default:
319                 return false;
320         }
321
322         if (tmpic == ic)
323                 return false;
324
325         pi.pain.text(x, y, char(tmpic), pi.base.font);
326         return true;
327 }
328
329
330 void InsetLatexAccent::drawAccent(PainterInfo const & pi, int x, int y,
331         char_type accent) const
332 {
333         LyXFont const & font = pi.base.font;
334         frontend::FontMetrics const & fm =      theFontMetrics(font);
335
336         x -= fm.center(accent);
337         y -= fm.ascent(ic);
338         y -= fm.descent(accent);
339         y -= fm.height(accent) / 2;
340         pi.pain.text(x, y, accent, font);
341 }
342
343
344 void InsetLatexAccent::draw(PainterInfo & pi, int x, int baseline) const
345 {
346         if (lyxrc.font_norm_type == LyXRC::ISO_8859_9)
347                 if (displayISO8859_9(pi, x, baseline))
348                         return;
349
350         // All the manually drawn accents in this function could use an
351         // overhaul. Different ways of drawing (what metrics to use)
352         // should also be considered.
353
354         LyXFont font = pi.base.font;
355         if (lyxrc.font_norm_type == LyXRC::ISO_10646_1)
356                 font.setLanguage(english_language);
357
358         frontend::FontMetrics const & fm =      theFontMetrics(font);
359
360         if (candisp) {
361                 int x2 = int(x + (fm.rbearing(ic) - fm.lbearing(ic)) / 2);
362                 int hg;
363                 int y;
364                 if (plusasc) {
365                         // mark at the top
366                         hg = fm.maxDescent();
367                         y = baseline - dim_.asc;
368                         if (font.shape() == LyXFont::ITALIC_SHAPE)
369                                 x2 += int(0.8 * hg); // italic
370                 } else {
371                         // at the bottom
372                         hg = dim_.des;
373                         y = baseline;
374                 }
375
376                 double hg35 = hg * 0.6;
377
378                 // display with proper accent mark
379                 // first the letter
380                 pi.pain.text(x, baseline, ic, font);
381
382                 if (remdot) {
383                         int tmpvar = baseline - fm.ascent('i');
384                         int tmpx = 0;
385                         if (font.shape() == LyXFont::ITALIC_SHAPE)
386                                 tmpx += int(0.8 * hg); // italic
387                         lyxerr[Debug::KEY] << "Removing dot." << endl;
388                         // remove the dot first
389                         pi.pain.fillRectangle(x + tmpx, tmpvar, dim_.wid,
390                                            fm.ascent('i') -
391                                            fm.ascent('x') - 1,
392                                            backgroundColor());
393                         // the five lines below is a simple hack to
394                         // make the display of accent 'i' and 'j'
395                         // better. It makes the accent be written
396                         // closer to the top of the dot-less 'i' or 'j'.
397                         char tmpic = ic; // store the ic when we
398                         ic = 'x';        // calculates the ascent of
399 #ifdef WITH_WARNINGS
400 #warning metrics?
401 #endif
402                         int asc = ascent(); // the dot-less version (here: 'x')
403                         ic = tmpic;      // set the orig ic back
404                         y = baseline - asc; // update to new y coord.
405                 }
406
407                 // now the rest - draw within (x, y, x + wid, y + hg)
408                 switch (modtype) {
409                 case ACUTE:
410                         //drawAccent(pi, x2, baseline, '\xB4');
411                         drawAccent(pi, x2, baseline, 0xB4);
412                         break;
413
414                 case GRAVE:
415                         //drawAccent(pi, x2, baseline, '\x60');
416                         drawAccent(pi, x2, baseline, 0x60);
417                         break;
418
419                 case MACRON:
420                         //drawAccent(pi, x2, baseline, '\xAF');
421                         drawAccent(pi, x2, baseline, 0xAF);
422                         break;
423
424                 case TILDE:
425                         drawAccent(pi, x2, baseline, '~');
426                         break;
427
428                 case UNDERBAR: {
429                         char_type const underbar = 0x5F; //('\x5F');
430                         pi.pain.text(x2 - fm.center(underbar),
431                                      baseline, underbar, font);
432                         break;
433                 }
434
435                 case CEDILLA: {
436                         char_type const cedilla = 0xB8; //('\xB8');
437                         pi.pain.text(x2  - fm.center(cedilla),
438                                      baseline, cedilla, font);
439                         break;
440                 }
441
442                 case UNDERDOT:
443                         pi.pain.text(x2  - fm.center('.'),
444                                   int(baseline + 1.5 * fm.height('.')),
445                                   '.', font);
446                         break;
447
448                 case DOT:
449                         drawAccent(pi, x2, baseline, '.');
450                         break;
451
452                 case CIRCLE:
453                         //drawAccent(pi, x2, baseline, '\xB0');
454                         drawAccent(pi, x2, baseline, 0xB0);
455                         break;
456
457                 case TIE:
458                         pi.pain.arc(int(x2 + hg35), y + hg / 2, 2 * hg, hg, 0, 360 * 32,
459                                     LColor::foreground);
460                         break;
461
462                 case BREVE:
463                         pi.pain.arc(int(x2 - hg / 2), y, hg, hg, 0, -360*32,
464                                     LColor::foreground);
465                         break;
466
467                 case CARON: {
468                         int xp[3], yp[3];
469                         xp[0] = int(x2 - hg35);    yp[0] = int(y + hg35);
470                         xp[1] = int(x2);           yp[1] = int(y + hg);
471                         xp[2] = int(x2 + hg35);    yp[2] = int(y + hg35);
472                         pi.pain.lines(xp, yp, 3, LColor::foreground);
473                         break;
474                 }
475
476                 case SPECIAL_CARON: {
477                         switch (ic) {
478                                 case 'L': dim_.wid = int(4.0 * dim_.wid / 5.0); break;
479                                 case 't': y -= int(hg35 / 2.0); break;
480                         }
481                         int xp[3], yp[3];
482                         xp[0] = int(x + dim_.wid);
483                         yp[0] = int(y + hg35 + hg);
484
485                         xp[1] = int(x + dim_.wid + (hg35 / 2.0));
486                         yp[1] = int(y + hg + (hg35 / 2.0));
487
488                         xp[2] = int(x + dim_.wid + (hg35 / 2.0));
489                         yp[2] = y + int(hg);
490
491                         pi.pain.lines(xp, yp, 3, LColor::foreground);
492                         break;
493                 }
494
495                 case HUNGARIAN_UMLAUT:
496                         drawAccent(pi, x2, baseline, 0x02DD);
497                         break;
498
499                 case UMLAUT:
500                         drawAccent(pi, x2, baseline, '"');
501                         break;
502
503                 case CIRCUMFLEX:
504                         drawAccent(pi, x2, baseline, '\x5E');
505                         break;
506
507                 case OGONEK: {
508                         // this does probably not look like an ogonek, so
509                         // it should certainly be refined
510                         int xp[4], yp[4];
511
512                         xp[0] = x2;
513                         yp[0] = y;
514
515                         xp[1] = x2;
516                         yp[1] = y + int(hg35);
517
518                         xp[2] = int(x2 - hg35);
519                         yp[2] = y + hg / 2;
520
521                         xp[3] = x2 + hg / 4;
522                         yp[3] = y + int(hg);
523
524                         pi.pain.lines(xp, yp, 4, LColor::foreground);
525                         break;
526                 }
527
528                 case lSLASH:
529                 case LSLASH: {
530                         int xp[2], yp[2];
531
532                         xp[0] = x;
533                         yp[0] = y + int(3 * hg);
534
535                         xp[1] = int(x + dim_.wid * 0.75);
536                         yp[1] = y + int(hg);
537
538                         pi.pain.lines(xp, yp, 2, LColor::foreground);
539                         break;
540                 }
541
542                 case DOT_LESS_I: // dotless-i
543                 case DOT_LESS_J: // dotless-j
544                         // nothing to do for these
545                         break;
546                 }
547
548         } else {
549                 pi.pain.fillRectangle(x + 1,
550                                       baseline - dim_.asc + 1, dim_.wid - 2,
551                                       dim_.asc + dim_.des - 2,
552                                       backgroundColor());
553                 pi.pain.rectangle(x + 1, baseline - dim_.asc + 1,
554                                   dim_.wid - 2, dim_.asc + dim_.des - 2,
555                                   LColor::foreground);
556                 docstring dcon(contents.begin(), contents.end());
557                 pi.pain.text(x + 2, baseline, dcon, font);
558         }
559 }
560
561
562 void InsetLatexAccent::write(Buffer const &, ostream & os) const
563 {
564         os << "\\i " << contents << "\n";
565 }
566
567
568 void InsetLatexAccent::read(Buffer const &, LyXLex & lex)
569 {
570         lex.eatLine();
571         contents = lex.getString();
572         checkContents();
573 }
574
575
576 int InsetLatexAccent::latex(Buffer const &, odocstream & os,
577                             OutputParams const &) const
578 {
579         os << from_ascii(contents);
580         return 0;
581 }
582
583
584 int InsetLatexAccent::plaintext(Buffer const &, odocstream & os,
585                             OutputParams const &) const
586 {
587         os << from_ascii(contents);
588         return 0;
589 }
590
591
592 int InsetLatexAccent::docbook(Buffer const &, odocstream & os,
593                               OutputParams const &) const
594 {
595         // FIXME UNICODE
596         os << from_ascii(contents);
597         return 0;
598 }
599
600
601 int InsetLatexAccent::textString(Buffer const & buf, odocstream & os,
602                        OutputParams const & op) const
603 {
604         return plaintext(buf, os, op);
605 }
606
607
608 bool InsetLatexAccent::directWrite() const
609 {
610         return true;
611 }
612
613
614 InsetBase::Code InsetLatexAccent::lyxCode() const
615 {
616         return InsetBase::ACCENT_CODE;
617 }
618
619
620 ostream & operator<<(ostream & o, InsetLatexAccent::ACCENT_TYPES at)
621 {
622         return o << int(at);
623 }
624
625
626 } // namespace lyx