2 * \file InsetSpecialChar.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup Nielsen
7 * \author Jean-Marc Lasgouttes
8 * \author Lars Gullik Bjønnes
10 * Full author contact details are available in file CREDITS.
15 #include "InsetSpecialChar.h"
17 #include "Dimension.h"
21 #include "LaTeXFeatures.h"
22 #include "MetricsInfo.h"
23 #include "output_xhtml.h"
25 #include "texstream.h"
27 #include "frontends/FontMetrics.h"
28 #include "frontends/NullPainter.h"
29 #include "frontends/Painter.h"
31 #include "support/debug.h"
32 #include "support/docstream.h"
33 #include "support/Lexer.h"
42 InsetSpecialChar::InsetSpecialChar(Kind k)
43 : Inset(nullptr), kind_(k)
47 InsetSpecialChar::Kind InsetSpecialChar::kind() const
53 docstring InsetSpecialChar::toolTip(BufferView const &, int, int) const
58 message = from_ascii("Optional Line Break (ZWSP)");
61 message = from_ascii("Ligature Break (ZWNJ)");
64 message = from_ascii("End of Sentence");
67 message = from_ascii("Hyphenation Point");
70 message = from_ascii("Breakable Slash");
73 message = from_ascii("Protected Hyphen (SHY)");
81 // no tooltip for these ones.
88 int InsetSpecialChar::rowFlags() const
94 // these are the elements that allow line breaking
113 // helper function: draw text and update x.
114 void drawChar(PainterInfo & pi, int & x, int const y, char_type ch)
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);
123 void drawLogo(PainterInfo & pi, int & x, int const y, InsetSpecialChar::Kind kind)
125 FontInfo const & font = pi.base.font;
126 int const em = theFontMetrics(font).em();
128 case InsetSpecialChar::PHRASE_LYX:
130 * \providecommand{\LyX}{L\kern-.1667em\lower.25em\hbox{Y}\kern-.125emX\\@};
132 drawChar(pi, x, y, 'L');
134 drawChar(pi, x, y + em / 4, 'Y');
136 drawChar(pi, x, y, 'X');
139 case InsetSpecialChar::PHRASE_TEX: {
141 * \def\TeX{T\kern-.1667em\lower.5ex\hbox{E}\kern-.125emX\@}
143 int const ex = theFontMetrics(font).xHeight();
144 drawChar(pi, x, y, 'T');
146 drawChar(pi, x, y + ex / 2, 'E');
148 drawChar(pi, x, y, 'X');
151 case InsetSpecialChar::PHRASE_LATEX2E:
153 * \DeclareRobustCommand{\LaTeXe}{\mbox{\m@th
154 * \if b\expandafter\@car\f@series\@nil\boldmath\fi
155 * \LaTeX\kern.15em2$_{\textstyle\varepsilon}$}}
157 drawLogo(pi, x, y, InsetSpecialChar::PHRASE_LATEX);
159 drawChar(pi, x, y, '2');
160 // ε U+03B5 GREEK SMALL LETTER EPSILON
161 drawChar(pi, x, y + em / 4, char_type(0x03b5));
164 case InsetSpecialChar::PHRASE_LATEX: {
166 * \DeclareRobustCommand{\LaTeX}{L\kern-.36em%
168 * \vbox to\ht\z@{\hbox{\check@mathfonts
169 * \fontsize\sf@size\z@
170 * \math@fontsfalse\selectfont
177 drawChar(pi, x, y, 'L');
179 PainterInfo pi2 = pi;
180 pi2.base.font.decSize().decSize();
181 drawChar(pi2, x, y - em / 5, 'A');
183 drawLogo(pi, x, y, InsetSpecialChar::PHRASE_TEX);
187 LYXERR0("No information for drawing logo " << kind);
194 void InsetSpecialChar::metrics(MetricsInfo & mi, Dimension & dim) const
196 frontend::FontMetrics const & fm = theFontMetrics(mi.base.font);
197 dim.asc = fm.maxAscent();
204 dim.asc = fm.xHeight();
205 dim.des = fm.descent('g');
206 dim.wid = fm.em() / 8;
211 case END_OF_SENTENCE:
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;
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));
228 dim.wid = fm.width(from_ascii("-"));
230 dim.wid -= 2; // to make it look shorter
234 dim.des = fm.descent(s[0]);
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_);
254 dim.wid = fm.width(s);
258 void InsetSpecialChar::draw(PainterInfo & pi, int x, int y) const
260 FontInfo font = pi.base.font;
265 font.setColor(Color_special);
266 pi.pain.text(x, y, char_type('-'), font);
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);
283 font.setColor(Color_special);
284 pi.pain.text(x, y, char_type('|'), font);
287 case END_OF_SENTENCE:
289 font.setColor(Color_special);
290 pi.pain.text(x, y, char_type('.'), font);
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.
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);
311 frontend::FontMetrics const & fm = theFontMetrics(font);
313 // There is a \thinspace on each side of the triangle
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);
324 font.setColor(Color_special);
325 pi.pain.text(x, y, char_type('/'), font);
330 font.setColor(Color_latex);
331 pi.pain.text(x, y, char_type('-'), font);
338 drawLogo(pi, x, y, kind_);
344 void InsetSpecialChar::write(ostream & os) const
349 command = "softhyphen";
352 command = "allowbreak";
355 command = "ligaturebreak";
357 case END_OF_SENTENCE:
358 command = "endofsentence";
364 command = "menuseparator";
367 command = "breakableslash";
370 command = "nobreakdash";
385 os << "\\SpecialChar " << command << "\n";
389 void InsetSpecialChar::read(Lexer & lex)
392 string const command = lex.getString();
394 if (command == "softhyphen")
396 else if (command == "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")
404 else if (command == "menuseparator")
405 kind_ = MENU_SEPARATOR;
406 else if (command == "breakableslash")
408 else if (command == "nobreakdash")
410 else if (command == "LyX")
412 else if (command == "TeX")
414 else if (command == "LaTeX2e")
415 kind_ = PHRASE_LATEX2E;
416 else if (command == "LaTeX")
417 kind_ = PHRASE_LATEX;
419 lex.printError("InsetSpecialChar: Unknown kind: `$$Token'");
423 void InsetSpecialChar::latex(otexstream & os,
424 OutputParams const & rp) const
426 bool const rtl = rp.local_font && rp.local_font->isRightToLeft();
427 bool const utf8 = rp.encoding->iconvName() == "UTF-8";
429 string lswitche = "";
430 if (rtl && !rp.use_polyglossia) {
433 if (getLocalOrDefaultLang(rp)->lang() == "arabic_arabi"
434 || getLocalOrDefaultLang(rp)->lang() == "farsi")
435 lswitch = "\\textLR{";
443 // U+200B not yet supported by utf8 inputenc
444 os << "\\LyXZeroWidthSpace" << termcmd;
448 // U+200C ZERO WIDTH NON-JOINER
451 os << "\\textcompwordmark" << termcmd;
453 case END_OF_SENTENCE:
457 os << "\\ldots" << termcmd;
467 os << "\\slash" << termcmd;
472 os << "\\nobreakdash-";
477 os << lswitch << "\\LyX" << termcmd << lswitche;
482 os << lswitch << "\\TeX" << termcmd << lswitche;
487 os << lswitch << "\\LaTeXe" << termcmd << lswitche;
492 os << lswitch << "\\LaTeX" << termcmd << lswitche;
498 int InsetSpecialChar::plaintext(odocstringstream & os,
499 OutputParams const &, size_t) const
505 // U+200B ZERO WIDTH SPACE (ZWSP)
509 // U+200C ZERO WIDTH NON-JOINER
512 case END_OF_SENTENCE:
516 // … U+2026 HORIZONTAL ELLIPSIS
526 // ‑ U+2011 NON-BREAKING HYPHEN
537 // ε U+03B5 GREEK SMALL LETTER EPSILON
549 string specialCharKindToXMLEntity(InsetSpecialChar::Kind kind) {
551 case InsetSpecialChar::Kind::HYPHENATION:
554 case InsetSpecialChar::Kind::ALLOWBREAK:
557 case InsetSpecialChar::Kind::LIGATURE_BREAK:
558 // Zero width non-joiner
560 case InsetSpecialChar::Kind::END_OF_SENTENCE:
562 case InsetSpecialChar::Kind::LDOTS:
565 case InsetSpecialChar::Kind::MENU_SEPARATOR:
566 // ⇒, right arrow.
568 case InsetSpecialChar::Kind::SLASH:
569 // ⁄, fractional slash.
571 case InsetSpecialChar::Kind::NOBREAKDASH:
572 // Non-breaking hyphen.
574 case InsetSpecialChar::Kind::PHRASE_LYX:
576 case InsetSpecialChar::Kind::PHRASE_TEX:
578 case InsetSpecialChar::Kind::PHRASE_LATEX2E:
579 // Lower-case epsilon.
580 return "LaTeX2ε";
581 case InsetSpecialChar::Kind::PHRASE_LATEX:
590 void InsetSpecialChar::docbook(XMLStream & xs, OutputParams const &) const
592 xs << XMLStream::ESCAPE_NONE << from_ascii(specialCharKindToXMLEntity(kind_));
596 docstring InsetSpecialChar::xhtml(XMLStream & xs, OutputParams const &) const
598 xs << XMLStream::ESCAPE_NONE << from_ascii(specialCharKindToXMLEntity(kind_));
603 void InsetSpecialChar::toString(odocstream & os) const
608 // Do not output ZERO WIDTH SPACE and ZERO WIDTH NON JOINER here
609 // Spell checker would choke on it.
614 odocstringstream ods;
615 plaintext(ods, OutputParams(nullptr));
620 void InsetSpecialChar::forOutliner(docstring & os, size_t const,
623 odocstringstream ods;
624 plaintext(ods, OutputParams(nullptr));
629 void InsetSpecialChar::validate(LaTeXFeatures & features) const
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");
642 bool InsetSpecialChar::isChar() const
644 return kind_ != HYPHENATION && kind_ != LIGATURE_BREAK;
648 bool InsetSpecialChar::isLetter() const
650 return kind_ == HYPHENATION || kind_ == LIGATURE_BREAK
651 || kind_ == NOBREAKDASH
652 || kind_ == PHRASE_LYX || kind_ == PHRASE_LATEX
653 || kind_ == PHRASE_TEX || kind_ == PHRASE_LATEX2E;
657 bool InsetSpecialChar::isLineSeparator() const
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;