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