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