]> git.lyx.org Git - lyx.git/blob - src/insets/InsetSpecialChar.cpp
Use a unicode character for menu separator on screen
[lyx.git] / src / insets / InsetSpecialChar.cpp
1 /**
2  * \file InsetSpecialChar.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup Nielsen
7  * \author Jean-Marc Lasgouttes
8  * \author Lars Gullik Bjønnes
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetSpecialChar.h"
16
17 #include "Dimension.h"
18 #include "Font.h"
19 #include "Language.h"
20 #include "LaTeXFeatures.h"
21 #include "Lexer.h"
22 #include "MetricsInfo.h"
23 #include "output_xhtml.h"
24 #include "texstream.h"
25
26 #include "frontends/FontMetrics.h"
27 #include "frontends/Painter.h"
28
29 #include "support/debug.h"
30 #include "support/docstream.h"
31
32 using namespace std;
33
34 namespace lyx {
35
36
37 InsetSpecialChar::InsetSpecialChar(Kind k)
38         : Inset(0), kind_(k)
39 {}
40
41
42 InsetSpecialChar::Kind InsetSpecialChar::kind() const
43 {
44         return kind_;
45 }
46
47
48 namespace {
49
50 int logoWidth(FontInfo const & font, InsetSpecialChar::Kind kind) {
51         frontend::FontMetrics const & fm = theFontMetrics(font);
52         int const em = fm.em();
53         int width = 0;
54         // See drawlogo() below to understand what this does.
55         switch (kind) {
56         case InsetSpecialChar::PHRASE_LYX:
57                 width = fm.width(from_ascii("L")) - em / 6
58                         + fm.width(from_ascii("Y")) - em / 8
59                         + fm.width(from_ascii("X"));
60                 break;
61
62         case InsetSpecialChar::PHRASE_TEX:
63                 width = fm.width(from_ascii("T")) - em / 6
64                         + fm.width(from_ascii("E")) - em / 8
65                         + fm.width(from_ascii("X"));
66                 break;
67
68         case InsetSpecialChar::PHRASE_LATEX2E:
69                 width = logoWidth(font, InsetSpecialChar::PHRASE_LATEX)
70                         + 3 * em / 20
71                         + fm.width(from_ascii("2") + char_type(0x03b5));
72                 break;
73         case InsetSpecialChar::PHRASE_LATEX: {
74                 FontInfo smaller = font;
75                 smaller.decSize().decSize();
76                 width = fm.width(from_ascii("L")) - 9 * em / 25
77                         + theFontMetrics(smaller).width(from_ascii("A")) - 3 * em / 20
78                         + logoWidth(font, InsetSpecialChar::PHRASE_TEX);
79                 break;
80         }
81         default:
82                 LYXERR0("No information for computing width of logo " << kind);
83         }
84
85         return width;
86 }
87
88 } // namespace
89
90 docstring InsetSpecialChar::toolTip(BufferView const &, int, int) const
91 {
92         docstring message;
93         switch (kind_) {
94                 case ALLOWBREAK:
95                         message = from_ascii("Optional Line Break (ZWSP)");
96                         break;
97                 case LIGATURE_BREAK:
98                         message = from_ascii("Ligature Break (ZWNJ)");
99                         break;
100                 case END_OF_SENTENCE:
101                         message = from_ascii("End of Sentence");
102                         break;
103                 case HYPHENATION:
104                         message = from_ascii("Hyphenation Point");
105                         break;
106                 case SLASH:
107                         message = from_ascii("Breakable Slash");
108                         break;
109                 case NOBREAKDASH:
110                         message = from_ascii("Protected Hyphen (SHY)");
111                         break;
112                 case LDOTS:
113                 case MENU_SEPARATOR:
114                 case PHRASE_LYX:
115                 case PHRASE_TEX:
116                 case PHRASE_LATEX2E:
117                 case PHRASE_LATEX:
118                         // no tooltip for these ones.
119                         break;
120         }
121         return message;
122 }
123
124
125 void InsetSpecialChar::metrics(MetricsInfo & mi, Dimension & dim) const
126 {
127         frontend::FontMetrics const & fm =
128                 theFontMetrics(mi.base.font);
129         dim.asc = fm.maxAscent();
130         dim.des = 0;
131         dim.wid = 0;
132
133         docstring s;
134         switch (kind_) {
135                 case ALLOWBREAK:
136                         dim.asc = fm.ascent('x');
137                         dim.des = fm.descent('g');
138                         dim.wid = fm.em() / 8;
139                         break;
140                 case LIGATURE_BREAK:
141                         s = from_ascii("|");
142                         break;
143                 case END_OF_SENTENCE:
144                         s = from_ascii(".");
145                         break;
146                 case LDOTS:
147                         s = from_ascii(". . .");
148                         break;
149                 case MENU_SEPARATOR:
150                         // ▹  U+25B9 WHITE RIGHT-POINTING SMALL TRIANGLE
151                         // There is a \thinspace on each side of the triangle
152                         dim.wid = 2 * fm.em() / 6 + fm.width(char_type(0x25B9));
153                         break;
154                 case HYPHENATION:
155                         dim.wid = fm.width(from_ascii("-"));
156                         if (dim.wid > 5)
157                                 dim.wid -= 2; // to make it look shorter
158                         break;
159                 case SLASH:
160                         s = from_ascii("/");
161                         dim.des = fm.descent(s[0]);
162                         break;
163                 case NOBREAKDASH:
164                         s = from_ascii("-");
165                         break;
166                 case PHRASE_LYX:
167                 case PHRASE_TEX:
168                 case PHRASE_LATEX2E:
169                 case PHRASE_LATEX:
170                         dim.asc = fm.maxAscent();
171                         dim.des = fm.maxDescent();
172                         dim.wid = logoWidth(mi.base.font, kind_);
173                         break;
174         }
175         if (dim.wid == 0)
176                 dim.wid = fm.width(s);
177 }
178
179
180 namespace {
181
182 // helper function: draw text and update x.
183 void drawChar(PainterInfo & pi, int & x, int const y, char_type ch)
184 {
185         FontInfo font = pi.base.font;
186         font.setPaintColor(pi.textColor(font.realColor()));
187         pi.pain.text(x, y, ch, font);
188         x += theFontMetrics(font).width(ch);
189 }
190
191
192 void drawLogo(PainterInfo & pi, int & x, int const y, InsetSpecialChar::Kind kind)
193 {
194         FontInfo const & font = pi.base.font;
195         int const em = theFontMetrics(font).em();
196         switch (kind) {
197         case InsetSpecialChar::PHRASE_LYX:
198                 /** Reference macro:
199                  *  \providecommand{\LyX}{L\kern-.1667em\lower.25em\hbox{Y}\kern-.125emX\\@};
200                  */
201                 drawChar(pi, x, y, 'L');
202                 x -= em / 6;
203                 drawChar(pi, x, y + em / 4, 'Y');
204                 x -= em / 8;
205                 drawChar(pi, x, y, 'X');
206                 break;
207
208         case InsetSpecialChar::PHRASE_TEX: {
209                 /** Reference macro:
210                  *  \def\TeX{T\kern-.1667em\lower.5ex\hbox{E}\kern-.125emX\@}
211                  */
212                 int const ex = theFontMetrics(font).ascent('x');
213                 drawChar(pi, x, y, 'T');
214                 x -= em / 6;
215                 drawChar(pi, x, y + ex / 2, 'E');
216                 x -= em / 8;
217                 drawChar(pi, x, y, 'X');
218                 break;
219         }
220         case InsetSpecialChar::PHRASE_LATEX2E:
221                 /** Reference macro:
222                  *  \DeclareRobustCommand{\LaTeXe}{\mbox{\m@th
223                  *    \if b\expandafter\@car\f@series\@nil\boldmath\fi
224                  *    \LaTeX\kern.15em2$_{\textstyle\varepsilon}$}}
225                  */
226                 drawLogo(pi, x, y, InsetSpecialChar::PHRASE_LATEX);
227                 x += 3 * em / 20;
228                 drawChar(pi, x, y, '2');
229                 drawChar(pi, x, y + em / 4, char_type(0x03b5));
230                 break;
231
232         case InsetSpecialChar::PHRASE_LATEX: {
233                 /** Reference macro:
234                  * \DeclareRobustCommand{\LaTeX}{L\kern-.36em%
235                  *        {\sbox\z@ T%
236                  *         \vbox to\ht\z@{\hbox{\check@mathfonts
237                  *                              \fontsize\sf@size\z@
238                  *                              \math@fontsfalse\selectfont
239                  *                              A}%
240                  *                        \vss}%
241                  *        }%
242                  *        \kern-.15em%
243                  *        \TeX}
244                  */
245                 drawChar(pi, x, y, 'L');
246                 x -= 9 * em / 25;
247                 PainterInfo pi2 = pi;
248                 pi2.base.font.decSize().decSize();
249                 drawChar(pi2, x, y - em / 5, 'A');
250                 x -= 3 * em / 20;
251                 drawLogo(pi, x, y, InsetSpecialChar::PHRASE_TEX);
252                 break;
253         }
254         default:
255                 LYXERR0("No information for drawing logo " << kind);
256         }
257 }
258
259 } // namespace
260
261 void InsetSpecialChar::draw(PainterInfo & pi, int x, int y) const
262 {
263         FontInfo font = pi.base.font;
264
265         switch (kind_) {
266         case HYPHENATION:
267         {
268                 font.setColor(Color_special);
269                 pi.pain.text(x, y, char_type('-'), font);
270                 break;
271         }
272         case ALLOWBREAK:
273         {
274                 // A small vertical line
275                 int const asc = theFontMetrics(pi.base.font).ascent('x');
276                 int const desc = theFontMetrics(pi.base.font).descent('g');
277                 int const x0 = x; // x + 1; // FIXME: incline,
278                 int const x1 = x; // x - 1; // similar to LibreOffice?
279                 int const y0 = y + desc;
280                 int const y1 = y - asc / 3;
281                 pi.pain.line(x0, y1, x1, y0, Color_special);
282                 break;
283         }
284         case LIGATURE_BREAK:
285         {
286                 font.setColor(Color_special);
287                 pi.pain.text(x, y, char_type('|'), font);
288                 break;
289         }
290         case END_OF_SENTENCE:
291         {
292                 font.setColor(Color_special);
293                 pi.pain.text(x, y, char_type('.'), font);
294                 break;
295         }
296         case LDOTS:
297         {
298                 font.setColor(Color_special);
299                 string ell = ". . . ";
300                 docstring dell(ell.begin(), ell.end());
301                 pi.pain.text(x, y, dell, font);
302                 break;
303         }
304         case MENU_SEPARATOR:
305         {
306                 frontend::FontMetrics const & fm =
307                         theFontMetrics(font);
308
309                 // There is a \thinspace on each side of the triangle
310                 x += fm.em() / 6;
311                 // ▹ U+25B9 WHITE RIGHT-POINTING SMALL TRIANGLE
312                 // ◃ U+25C3 WHITE LEFT-POINTING SMALL TRIANGLE
313                 char_type const c = pi.ltr_pos ? 0x25B9 : 0x25C3;
314                 font.setColor(Color_special);
315                 pi.pain.text(x, y, c, font);
316                 break;
317         }
318         case SLASH:
319         {
320                 font.setColor(Color_special);
321                 pi.pain.text(x, y, char_type('/'), font);
322                 break;
323         }
324         case NOBREAKDASH:
325         {
326                 font.setColor(Color_latex);
327                 pi.pain.text(x, y, char_type('-'), font);
328                 break;
329         }
330         case PHRASE_LYX:
331         case PHRASE_TEX:
332         case PHRASE_LATEX2E:
333         case PHRASE_LATEX:
334                 drawLogo(pi, x, y, kind_);
335                 break;
336         }
337 }
338
339
340 void InsetSpecialChar::write(ostream & os) const
341 {
342         string command;
343         switch (kind_) {
344         case HYPHENATION:
345                 command = "softhyphen";
346                 break;
347         case ALLOWBREAK:
348                 command = "allowbreak";
349                 break;
350         case LIGATURE_BREAK:
351                 command = "ligaturebreak";
352                 break;
353         case END_OF_SENTENCE:
354                 command = "endofsentence";
355                 break;
356         case LDOTS:
357                 command = "ldots";
358                 break;
359         case MENU_SEPARATOR:
360                 command = "menuseparator";
361                 break;
362         case SLASH:
363                 command = "breakableslash";
364                 break;
365         case NOBREAKDASH:
366                 command = "nobreakdash";
367                 break;
368         case PHRASE_LYX:
369                 command = "LyX";
370                 break;
371         case PHRASE_TEX:
372                 command = "TeX";
373                 break;
374         case PHRASE_LATEX2E:
375                 command = "LaTeX2e";
376                 break;
377         case PHRASE_LATEX:
378                 command = "LaTeX";
379                 break;
380         }
381         os << "\\SpecialChar " << command << "\n";
382 }
383
384
385 void InsetSpecialChar::read(Lexer & lex)
386 {
387         lex.next();
388         string const command = lex.getString();
389
390         if (command == "softhyphen")
391                 kind_ = HYPHENATION;
392         else if (command == "allowbreak")
393                 kind_ = ALLOWBREAK;
394         else if (command == "ligaturebreak")
395                 kind_ = LIGATURE_BREAK;
396         else if (command == "endofsentence")
397                 kind_ = END_OF_SENTENCE;
398         else if (command == "ldots")
399                 kind_ = LDOTS;
400         else if (command == "menuseparator")
401                 kind_ = MENU_SEPARATOR;
402         else if (command == "breakableslash")
403                 kind_ = SLASH;
404         else if (command == "nobreakdash")
405                 kind_ = NOBREAKDASH;
406         else if (command == "LyX")
407                 kind_ = PHRASE_LYX;
408         else if (command == "TeX")
409                 kind_ = PHRASE_TEX;
410         else if (command == "LaTeX2e")
411                 kind_ = PHRASE_LATEX2E;
412         else if (command == "LaTeX")
413                 kind_ = PHRASE_LATEX;
414         else
415                 lex.printError("InsetSpecialChar: Unknown kind: `$$Token'");
416 }
417
418
419 void InsetSpecialChar::latex(otexstream & os,
420                              OutputParams const & rp) const
421 {
422         bool const rtl = rp.local_font->isRightToLeft();
423         string lswitch = "";
424         string lswitche = "";
425         if (rtl && !rp.use_polyglossia) {
426                 lswitch = "\\L{";
427                 lswitche = "}";
428                 if (rp.local_font->language()->lang() == "arabic_arabi"
429                     || rp.local_font->language()->lang() == "farsi")
430                         lswitch = "\\textLR{";
431         }
432
433         switch (kind_) {
434         case HYPHENATION:
435                 os << "\\-";
436                 break;
437         case ALLOWBREAK:
438                 os << "\\LyXZeroWidthSpace" << termcmd;
439                 break;
440         case LIGATURE_BREAK:
441                 os << "\\textcompwordmark" << termcmd;
442                 break;
443         case END_OF_SENTENCE:
444                 os << "\\@.";
445                 break;
446         case LDOTS:
447                 os << "\\ldots" << termcmd;
448                 break;
449         case MENU_SEPARATOR:
450                 if (rtl)
451                         os << "\\lyxarrow*";
452                 else
453                         os << "\\lyxarrow";
454                 os << termcmd;
455                 break;
456         case SLASH:
457                 os << "\\slash" << termcmd;
458                 break;
459         case NOBREAKDASH:
460                 if (rp.moving_arg)
461                         os << "\\protect";
462                 os << "\\nobreakdash-";
463                 break;
464         case PHRASE_LYX:
465                 if (rp.moving_arg)
466                         os << "\\protect";
467                 os << lswitch << "\\LyX" << termcmd << lswitche;
468                 break;
469         case PHRASE_TEX:
470                 if (rp.moving_arg)
471                         os << "\\protect";
472                 os << lswitch << "\\TeX" << termcmd << lswitche;
473                 break;
474         case PHRASE_LATEX2E:
475                 if (rp.moving_arg)
476                         os << "\\protect";
477                 os << lswitch << "\\LaTeXe" << termcmd << lswitche;
478                 break;
479         case PHRASE_LATEX:
480                 if (rp.moving_arg)
481                         os << "\\protect";
482                 os << lswitch << "\\LaTeX" << termcmd << lswitche;
483                 break;
484         }
485 }
486
487
488 int InsetSpecialChar::plaintext(odocstringstream & os,
489         OutputParams const &, size_t) const
490 {
491         switch (kind_) {
492         case HYPHENATION:
493                 return 0;
494         case ALLOWBREAK:
495                 os.put(0x200b);
496                 return 1;
497         case LIGATURE_BREAK:
498                 os.put(0x200c);
499                 return 1;
500         case END_OF_SENTENCE:
501                 os << '.';
502                 return 1;
503         case LDOTS:
504                 os.put(0x2026);
505                 return 1;
506         case MENU_SEPARATOR:
507                 os << "->";
508                 return 2;
509         case SLASH:
510                 os << '/';
511                 return 1;
512         case NOBREAKDASH:
513                 os.put(0x2011);
514                 return 1;
515         case PHRASE_LYX:
516                 os << "LyX";
517                 return 3;
518         case PHRASE_TEX:
519                 os << "TeX";
520                 return 3;
521         case PHRASE_LATEX2E:
522                 os << "LaTeX2";
523                 os.put(0x03b5);
524                 return 7;
525         case PHRASE_LATEX:
526                 os << "LaTeX";
527                 return 5;
528         }
529         return 0;
530 }
531
532
533 int InsetSpecialChar::docbook(odocstream & os, OutputParams const &) const
534 {
535         switch (kind_) {
536         case HYPHENATION:
537                 break;
538         case ALLOWBREAK:
539                 os.put(0x200b);
540                 break;
541         case LIGATURE_BREAK:
542                 break;
543         case END_OF_SENTENCE:
544                 os << '.';
545                 break;
546         case LDOTS:
547                 os << "&hellip;";
548                 break;
549         case MENU_SEPARATOR:
550                 os << "&lyxarrow;";
551                 break;
552         case SLASH:
553                 os << '/';
554                 break;
555         case NOBREAKDASH:
556                 os << '-';
557                 break;
558         case PHRASE_LYX:
559                 os << "LyX";
560                 break;
561         case PHRASE_TEX:
562                 os << "TeX";
563                 break;
564         case PHRASE_LATEX2E:
565                 os << "LaTeX2";
566                 os.put(0x03b5);
567                 break;
568         case PHRASE_LATEX:
569                 os << "LaTeX";
570                 break;
571         }
572         return 0;
573 }
574
575
576 docstring InsetSpecialChar::xhtml(XHTMLStream & xs, OutputParams const &) const
577 {
578         switch (kind_) {
579         case HYPHENATION:
580                 break;
581         case ALLOWBREAK:
582                 xs << XHTMLStream::ESCAPE_NONE << "&#8203;";
583                 break;
584         case LIGATURE_BREAK:
585                 xs << XHTMLStream::ESCAPE_NONE << "&#8204;";
586                 break;
587         case END_OF_SENTENCE:
588                 xs << '.';
589                 break;
590         case LDOTS:
591                 xs << XHTMLStream::ESCAPE_NONE << "&hellip;";
592                 break;
593         case MENU_SEPARATOR:
594                 xs << XHTMLStream::ESCAPE_NONE << "&rArr;";
595                 break;
596         case SLASH:
597                 xs << XHTMLStream::ESCAPE_NONE << "&frasl;";
598                 break;
599         case NOBREAKDASH:
600                 xs << XHTMLStream::ESCAPE_NONE << "&#8209;";
601                 break;
602         case PHRASE_LYX:
603                 xs << "LyX";
604                 break;
605         case PHRASE_TEX:
606                 xs << "TeX";
607                 break;
608         case PHRASE_LATEX2E:
609                 xs << "LaTeX2" << XHTMLStream::ESCAPE_NONE << "&#x3b5;";
610                 break;
611         case PHRASE_LATEX:
612                 xs << "LaTeX";
613                 break;
614         }
615         return docstring();
616 }
617
618
619 void InsetSpecialChar::toString(odocstream & os) const
620 {
621         switch (kind_) {
622         case ALLOWBREAK:
623         case LIGATURE_BREAK:
624                 // Do not output ZERO WIDTH SPACE and ZERO WIDTH NON JOINER here
625                 // Spell checker would choke on it.
626                 return;
627         default:
628                 break;
629         }
630         odocstringstream ods;
631         plaintext(ods, OutputParams(0));
632         os << ods.str();
633 }
634
635
636 void InsetSpecialChar::forOutliner(docstring & os, size_t const,
637                                                                    bool const) const
638 {
639         odocstringstream ods;
640         plaintext(ods, OutputParams(0));
641         os += ods.str();
642 }
643
644
645 void InsetSpecialChar::validate(LaTeXFeatures & features) const
646 {
647         if (kind_ == ALLOWBREAK)
648                 features.require("lyxzerowidthspace");
649         if (kind_ == MENU_SEPARATOR)
650                 features.require("lyxarrow");
651         if (kind_ == NOBREAKDASH)
652                 features.require("amsmath");
653         if (kind_ == PHRASE_LYX)
654                 features.require("LyX");
655 }
656
657
658 bool InsetSpecialChar::isChar() const
659 {
660         return kind_ != HYPHENATION && kind_ != LIGATURE_BREAK;
661 }
662
663
664 bool InsetSpecialChar::isLetter() const
665 {
666         return kind_ == HYPHENATION || kind_ == LIGATURE_BREAK
667                 || kind_ == NOBREAKDASH;
668 }
669
670
671 bool InsetSpecialChar::isLineSeparator() const
672 {
673 #if 0
674         // this would be nice, but it does not work, since
675         // Paragraph::stripLeadingSpaces nukes the characters which
676         // have this property. I leave the code here, since it should
677         // eventually be made to work. (JMarc 20020327)
678         return kind_ == HYPHENATION || kind_ == ALLOWBREAK
679             || kind_ == MENU_SEPARATOR || kind_ == SLASH;
680 #else
681         return false;
682 #endif
683 }
684
685
686 } // namespace lyx