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