X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2FInsetSpecialChar.cpp;h=5983c07d649906ad69c225ae005c2b8c2fa801a5;hb=8124e6c02ea1fd6779bb6c47ffe2bca2c8bd2d97;hp=3d32f406ea1bc812ac8553c303e8eeb6000c22af;hpb=e194c9ce279cc226962ab8f1ccb253893efd6d3c;p=lyx.git diff --git a/src/insets/InsetSpecialChar.cpp b/src/insets/InsetSpecialChar.cpp index 3d32f406ea..5983c07d64 100644 --- a/src/insets/InsetSpecialChar.cpp +++ b/src/insets/InsetSpecialChar.cpp @@ -15,14 +15,18 @@ #include "InsetSpecialChar.h" #include "Dimension.h" +#include "Encoding.h" #include "Font.h" +#include "Language.h" #include "LaTeXFeatures.h" #include "Lexer.h" #include "MetricsInfo.h" #include "output_xhtml.h" +#include "xml.h" #include "texstream.h" #include "frontends/FontMetrics.h" +#include "frontends/NullPainter.h" #include "frontends/Painter.h" #include "support/debug.h" @@ -34,7 +38,7 @@ namespace lyx { InsetSpecialChar::InsetSpecialChar(Kind k) - : Inset(0), kind_(k) + : Inset(nullptr), kind_(k) {} @@ -44,93 +48,61 @@ InsetSpecialChar::Kind InsetSpecialChar::kind() const } -namespace { - -int logoWidth(FontInfo const & font, InsetSpecialChar::Kind kind) { - frontend::FontMetrics const & fm = theFontMetrics(font); - int const em = fm.em(); - int width = 0; - // See drawlogo() below to understand what this does. - switch (kind) { - case InsetSpecialChar::PHRASE_LYX: - width = fm.width(from_ascii("L")) - em / 6 - + fm.width(from_ascii("Y")) - em / 8 - + fm.width(from_ascii("X")); - break; - - case InsetSpecialChar::PHRASE_TEX: - width = fm.width(from_ascii("T")) - em / 6 - + fm.width(from_ascii("E")) - em / 8 - + fm.width(from_ascii("X")); - break; - - case InsetSpecialChar::PHRASE_LATEX2E: - width = logoWidth(font, InsetSpecialChar::PHRASE_LATEX) - + 3 * em / 20 - + fm.width(from_ascii("2") + char_type(0x03b5)); - break; - case InsetSpecialChar::PHRASE_LATEX: { - FontInfo smaller = font; - smaller.decSize().decSize(); - width = fm.width(from_ascii("L")) - 9 * em / 25 - + theFontMetrics(smaller).width(from_ascii("A")) - 3 * em / 20 - + logoWidth(font, InsetSpecialChar::PHRASE_TEX); - break; - } - default: - LYXERR0("No information for computing width of logo " << kind); - } - - return width; -} - -} - - -void InsetSpecialChar::metrics(MetricsInfo & mi, Dimension & dim) const +docstring InsetSpecialChar::toolTip(BufferView const &, int, int) const { - frontend::FontMetrics const & fm = - theFontMetrics(mi.base.font); - dim.asc = fm.maxAscent(); - dim.des = fm.maxDescent(); - dim.wid = 0; - - docstring s; + docstring message; switch (kind_) { + case ALLOWBREAK: + message = from_ascii("Optional Line Break (ZWSP)"); + break; case LIGATURE_BREAK: - s = from_ascii("|"); + message = from_ascii("Ligature Break (ZWNJ)"); break; case END_OF_SENTENCE: - s = from_ascii("."); - break; - case LDOTS: - s = from_ascii(". . ."); - break; - case MENU_SEPARATOR: - s = from_ascii(" x "); + message = from_ascii("End of Sentence"); break; case HYPHENATION: - dim.wid = fm.width(from_ascii("-")); - if (dim.wid > 5) - dim.wid -= 2; // to make it look shorter + message = from_ascii("Hyphenation Point"); break; case SLASH: - s = from_ascii("/"); + message = from_ascii("Breakable Slash"); break; case NOBREAKDASH: - s = from_ascii("-"); + message = from_ascii("Protected Hyphen (SHY)"); break; + case LDOTS: + case MENU_SEPARATOR: case PHRASE_LYX: case PHRASE_TEX: case PHRASE_LATEX2E: case PHRASE_LATEX: - dim.wid = logoWidth(mi.base.font, kind_); + // no tooltip for these ones. break; } - if (dim.wid == 0) - dim.wid = fm.width(s); + return message; +} - setDimCache(mi, dim); + +int InsetSpecialChar::rowFlags() const +{ + switch (kind_) { + case ALLOWBREAK: + case HYPHENATION: + case SLASH: + // these are the elements that allow line breaking + return CanBreakAfter; + case NOBREAKDASH: + case END_OF_SENTENCE: + case LIGATURE_BREAK: + case LDOTS: + case MENU_SEPARATOR: + case PHRASE_LYX: + case PHRASE_TEX: + case PHRASE_LATEX2E: + case PHRASE_LATEX: + break; + } + return Inline; } @@ -139,8 +111,10 @@ namespace { // helper function: draw text and update x. void drawChar(PainterInfo & pi, int & x, int const y, char_type ch) { - pi.pain.text(x, y, ch, pi.base.font); - x += theFontMetrics(pi.base.font).width(ch); + FontInfo font = pi.base.font; + font.setPaintColor(pi.textColor(font.realColor())); + pi.pain.text(x, y, ch, font); + x += theFontMetrics(font).width(ch); } @@ -164,7 +138,7 @@ void drawLogo(PainterInfo & pi, int & x, int const y, InsetSpecialChar::Kind kin /** Reference macro: * \def\TeX{T\kern-.1667em\lower.5ex\hbox{E}\kern-.125emX\@} */ - int const ex = theFontMetrics(font).ascent('x'); + int const ex = theFontMetrics(font).xHeight(); drawChar(pi, x, y, 'T'); x -= em / 6; drawChar(pi, x, y + ex / 2, 'E'); @@ -181,6 +155,7 @@ void drawLogo(PainterInfo & pi, int & x, int const y, InsetSpecialChar::Kind kin drawLogo(pi, x, y, InsetSpecialChar::PHRASE_LATEX); x += 3 * em / 20; drawChar(pi, x, y, '2'); + // ε U+03B5 GREEK SMALL LETTER EPSILON drawChar(pi, x, y + em / 4, char_type(0x03b5)); break; @@ -211,8 +186,71 @@ void drawLogo(PainterInfo & pi, int & x, int const y, InsetSpecialChar::Kind kin } } +} // namespace + + +void InsetSpecialChar::metrics(MetricsInfo & mi, Dimension & dim) const +{ + frontend::FontMetrics const & fm = theFontMetrics(mi.base.font); + dim.asc = fm.maxAscent(); + dim.des = 0; + dim.wid = 0; + + docstring s; + switch (kind_) { + case ALLOWBREAK: + dim.asc = fm.xHeight(); + dim.des = fm.descent('g'); + dim.wid = fm.em() / 8; + break; + case LIGATURE_BREAK: + s = from_ascii("|"); + break; + case END_OF_SENTENCE: + s = from_ascii("."); + break; + case LDOTS: { + // see comment in draw(). + auto const fam = mi.base.font.family(); + // Multiplication by 3 is done here to limit rounding effects. + int const spc3 = fam == TYPEWRITER_FAMILY ? 0 : 3 * fm.width(char_type(' ')) / 2; + dim.wid = 3 * fm.width(char_type('.')) + spc3; + break; + } + case MENU_SEPARATOR: + // ▹ U+25B9 WHITE RIGHT-POINTING SMALL TRIANGLE + // There is a \thinspace on each side of the triangle + dim.wid = 2 * fm.em() / 6 + fm.width(char_type(0x25B9)); + break; + case HYPHENATION: + dim.wid = fm.width(from_ascii("-")); + if (dim.wid > 5) + dim.wid -= 2; // to make it look shorter + break; + case SLASH: + s = from_ascii("/"); + dim.des = fm.descent(s[0]); + break; + case NOBREAKDASH: + s = from_ascii("-"); + break; + case PHRASE_LYX: + case PHRASE_TEX: + case PHRASE_LATEX2E: + case PHRASE_LATEX: + dim.asc = fm.maxAscent(); + dim.des = fm.maxDescent(); + frontend::NullPainter np; + PainterInfo pi(mi.base.bv, np); + pi.base.font = mi.base.font; + drawLogo(pi, dim.wid, 0, kind_); + break; + } + if (dim.wid == 0) + dim.wid = fm.width(s); } + void InsetSpecialChar::draw(PainterInfo & pi, int x, int y) const { FontInfo font = pi.base.font; @@ -224,6 +262,18 @@ void InsetSpecialChar::draw(PainterInfo & pi, int x, int y) const pi.pain.text(x, y, char_type('-'), font); break; } + case ALLOWBREAK: + { + // A small vertical line + int const asc = theFontMetrics(pi.base.font).xHeight(); + int const desc = theFontMetrics(pi.base.font).descent('g'); + int const x0 = x; // x + 1; // FIXME: incline, + int const x1 = x; // x - 1; // similar to LibreOffice? + int const y0 = y + desc; + int const y1 = y - asc / 3; + pi.pain.line(x0, y1, x1, y0, Color_special); + break; + } case LIGATURE_BREAK: { font.setColor(Color_special); @@ -239,28 +289,30 @@ void InsetSpecialChar::draw(PainterInfo & pi, int x, int y) const case LDOTS: { font.setColor(Color_special); - string ell = ". . . "; - docstring dell(ell.begin(), ell.end()); - pi.pain.text(x, y, dell, font); + /* \textellipsis uses a \fontdimen3 is spacing. The TeXbook + * tells us that \fontdimen3 is the interword stretch, and + * that this is usually half a space. + */ + frontend::FontMetrics const & fm = theFontMetrics(font); + auto const fam = pi.base.font.family(); + int const spc = fam == TYPEWRITER_FAMILY ? 0 : fm.width(char_type(' ')) / 2; + int wid1 = fm.width(char_type('.')) + spc; + pi.pain.text(x, y, char_type('.'), font); + pi.pain.text(x + wid1, y, char_type('.'), font); + pi.pain.text(x + 2 * wid1, y, char_type('.'), font); break; } case MENU_SEPARATOR: { - frontend::FontMetrics const & fm = - theFontMetrics(font); - - // A triangle the width and height of an 'x' - int w = fm.width(char_type('x')); - int ox = fm.width(char_type(' ')) + x; - int h = fm.ascent(char_type('x')); - int xp[4], yp[4]; + frontend::FontMetrics const & fm = theFontMetrics(font); - xp[0] = ox; yp[0] = y; - xp[1] = ox; yp[1] = y - h; - xp[2] = ox + w; yp[2] = y - h/2; - xp[3] = ox; yp[3] = y; - - pi.pain.lines(xp, yp, 4, Color_special); + // There is a \thinspace on each side of the triangle + x += fm.em() / 6; + // ▹ U+25B9 WHITE RIGHT-POINTING SMALL TRIANGLE + // ◃ U+25C3 WHITE LEFT-POINTING SMALL TRIANGLE + char_type const c = pi.ltr_pos ? 0x25B9 : 0x25C3; + font.setColor(Color_special); + pi.pain.text(x, y, c, font); break; } case SLASH: @@ -285,7 +337,6 @@ void InsetSpecialChar::draw(PainterInfo & pi, int x, int y) const } -// In lyxf3 this will be just LaTeX void InsetSpecialChar::write(ostream & os) const { string command; @@ -293,6 +344,9 @@ void InsetSpecialChar::write(ostream & os) const case HYPHENATION: command = "softhyphen"; break; + case ALLOWBREAK: + command = "allowbreak"; + break; case LIGATURE_BREAK: command = "ligaturebreak"; break; @@ -328,7 +382,6 @@ void InsetSpecialChar::write(ostream & os) const } -// This function will not be necessary when lyx3 void InsetSpecialChar::read(Lexer & lex) { lex.next(); @@ -336,6 +389,8 @@ void InsetSpecialChar::read(Lexer & lex) if (command == "softhyphen") kind_ = HYPHENATION; + else if (command == "allowbreak") + kind_ = ALLOWBREAK; else if (command == "ligaturebreak") kind_ = LIGATURE_BREAK; else if (command == "endofsentence") @@ -364,27 +419,48 @@ void InsetSpecialChar::read(Lexer & lex) void InsetSpecialChar::latex(otexstream & os, OutputParams const & rp) const { + bool const rtl = rp.local_font->isRightToLeft(); + bool const utf8 = rp.encoding->iconvName() == "UTF-8"; + string lswitch = ""; + string lswitche = ""; + if (rtl && !rp.use_polyglossia) { + lswitch = "\\L{"; + lswitche = "}"; + if (rp.local_font->language()->lang() == "arabic_arabi" + || rp.local_font->language()->lang() == "farsi") + lswitch = "\\textLR{"; + } + switch (kind_) { case HYPHENATION: os << "\\-"; break; + case ALLOWBREAK: + // U+200B not yet supported by utf8 inputenc + os << "\\LyXZeroWidthSpace" << termcmd; + break; case LIGATURE_BREAK: - os << "\\textcompwordmark{}"; + if (utf8) + // U+200C ZERO WIDTH NON-JOINER + os.put(0x200c); + else + os << "\\textcompwordmark" << termcmd; break; case END_OF_SENTENCE: os << "\\@."; break; case LDOTS: - os << "\\ldots{}"; + os << "\\ldots" << termcmd; break; case MENU_SEPARATOR: - if (rp.local_font->isRightToLeft()) - os << "\\lyxarrow*{}"; + if (rtl) + os << "\\lyxarrow*"; else - os << "\\lyxarrow{}"; + os << "\\lyxarrow"; + os << termcmd; break; case SLASH: - os << "\\slash{}"; + os << "\\slash" << termcmd; break; case NOBREAKDASH: if (rp.moving_arg) @@ -394,22 +470,22 @@ void InsetSpecialChar::latex(otexstream & os, case PHRASE_LYX: if (rp.moving_arg) os << "\\protect"; - os << "\\LyX{}"; + os << lswitch << "\\LyX" << termcmd << lswitche; break; case PHRASE_TEX: if (rp.moving_arg) os << "\\protect"; - os << "\\TeX{}"; + os << lswitch << "\\TeX" << termcmd << lswitche; break; case PHRASE_LATEX2E: if (rp.moving_arg) os << "\\protect"; - os << "\\LaTeXe{}"; + os << lswitch << "\\LaTeXe" << termcmd << lswitche; break; case PHRASE_LATEX: if (rp.moving_arg) os << "\\protect"; - os << "\\LaTeX{}"; + os << lswitch << "\\LaTeX" << termcmd << lswitche; break; } } @@ -421,13 +497,19 @@ int InsetSpecialChar::plaintext(odocstringstream & os, switch (kind_) { case HYPHENATION: return 0; + case ALLOWBREAK: + // U+200B ZERO WIDTH SPACE (ZWSP) + os.put(0x200b); + return 1; case LIGATURE_BREAK: + // U+200C ZERO WIDTH NON-JOINER os.put(0x200c); return 1; case END_OF_SENTENCE: os << '.'; return 1; case LDOTS: + // … U+2026 HORIZONTAL ELLIPSIS os.put(0x2026); return 1; case MENU_SEPARATOR: @@ -437,6 +519,7 @@ int InsetSpecialChar::plaintext(odocstringstream & os, os << '/'; return 1; case NOBREAKDASH: + // ‑ U+2011 NON-BREAKING HYPHEN os.put(0x2011); return 1; case PHRASE_LYX: @@ -447,6 +530,7 @@ int InsetSpecialChar::plaintext(odocstringstream & os, return 3; case PHRASE_LATEX2E: os << "LaTeX2"; + // ε U+03B5 GREEK SMALL LETTER EPSILON os.put(0x03b5); return 7; case PHRASE_LATEX: @@ -457,81 +541,57 @@ int InsetSpecialChar::plaintext(odocstringstream & os, } -int InsetSpecialChar::docbook(odocstream & os, OutputParams const &) const -{ - switch (kind_) { - case HYPHENATION: - case LIGATURE_BREAK: - break; - case END_OF_SENTENCE: - os << '.'; - break; - case LDOTS: - os << "…"; - break; - case MENU_SEPARATOR: - os << "&lyxarrow;"; - break; - case SLASH: - os << '/'; - break; - case NOBREAKDASH: - os << '-'; - break; - case PHRASE_LYX: - os << "LyX"; - break; - case PHRASE_TEX: - os << "TeX"; - break; - case PHRASE_LATEX2E: - os << "LaTeX2"; - os.put(0x03b5); - break; - case PHRASE_LATEX: - os << "LaTeX"; - break; +namespace { +string specialCharKindToXMLEntity(InsetSpecialChar::Kind kind) { + switch (kind) { + case InsetSpecialChar::Kind::HYPHENATION: + // Soft hyphen. + return "­"; + case InsetSpecialChar::Kind::ALLOWBREAK: + // Zero-width space + return "​"; + case InsetSpecialChar::Kind::LIGATURE_BREAK: + // Zero width non-joiner + return "‌"; + case InsetSpecialChar::Kind::END_OF_SENTENCE: + return "."; + case InsetSpecialChar::Kind::LDOTS: + // … + return "…"; + case InsetSpecialChar::Kind::MENU_SEPARATOR: + // ⇒, right arrow. + return "⇒"; + case InsetSpecialChar::Kind::SLASH: + // ⁄, fractional slash. + return "⁄"; + case InsetSpecialChar::Kind::NOBREAKDASH: + // Non-breaking hyphen. + return "‑"; + case InsetSpecialChar::Kind::PHRASE_LYX: + return "LyX"; + case InsetSpecialChar::Kind::PHRASE_TEX: + return "TeX"; + case InsetSpecialChar::Kind::PHRASE_LATEX2E: + // Lower-case epsilon. + return "LaTeX2ε"; + case InsetSpecialChar::Kind::PHRASE_LATEX: + return "LaTeX"; + default: + return ""; } - return 0; +} } -docstring InsetSpecialChar::xhtml(XHTMLStream & xs, OutputParams const &) const +void InsetSpecialChar::docbook(XMLStream & xs, OutputParams const &) const { - switch (kind_) { - case HYPHENATION: - break; - case LIGATURE_BREAK: - xs << XHTMLStream::ESCAPE_NONE << "‌"; - break; - case END_OF_SENTENCE: - xs << '.'; - break; - case LDOTS: - xs << XHTMLStream::ESCAPE_NONE << "…"; - break; - case MENU_SEPARATOR: - xs << XHTMLStream::ESCAPE_NONE << "⇒"; - break; - case SLASH: - xs << XHTMLStream::ESCAPE_NONE << "⁄"; - break; - case NOBREAKDASH: - xs << XHTMLStream::ESCAPE_NONE << "‑"; - break; - case PHRASE_LYX: - xs << "LyX"; - break; - case PHRASE_TEX: - xs << "TeX"; - break; - case PHRASE_LATEX2E: - xs << "LaTeX2" << XHTMLStream::ESCAPE_NONE << "ε"; - break; - case PHRASE_LATEX: - xs << "LaTeX"; - break; - } + xs << XMLStream::ESCAPE_NONE << from_ascii(specialCharKindToXMLEntity(kind_)); +} + + +docstring InsetSpecialChar::xhtml(XMLStream & xs, OutputParams const &) const +{ + xs << XMLStream::ESCAPE_NONE << from_ascii(specialCharKindToXMLEntity(kind_)); return docstring(); } @@ -539,15 +599,16 @@ docstring InsetSpecialChar::xhtml(XHTMLStream & xs, OutputParams const &) const void InsetSpecialChar::toString(odocstream & os) const { switch (kind_) { + case ALLOWBREAK: case LIGATURE_BREAK: - // Do not output ZERO WIDTH NON JOINER here + // Do not output ZERO WIDTH SPACE and ZERO WIDTH NON JOINER here // Spell checker would choke on it. return; default: break; } odocstringstream ods; - plaintext(ods, OutputParams(0)); + plaintext(ods, OutputParams(nullptr)); os << ods.str(); } @@ -556,13 +617,15 @@ void InsetSpecialChar::forOutliner(docstring & os, size_t const, bool const) const { odocstringstream ods; - plaintext(ods, OutputParams(0)); + plaintext(ods, OutputParams(nullptr)); os += ods.str(); } void InsetSpecialChar::validate(LaTeXFeatures & features) const { + if (kind_ == ALLOWBREAK) + features.require("lyxzerowidthspace"); if (kind_ == MENU_SEPARATOR) features.require("lyxarrow"); if (kind_ == NOBREAKDASH) @@ -572,10 +635,18 @@ void InsetSpecialChar::validate(LaTeXFeatures & features) const } +bool InsetSpecialChar::isChar() const +{ + return kind_ != HYPHENATION && kind_ != LIGATURE_BREAK; +} + + bool InsetSpecialChar::isLetter() const { return kind_ == HYPHENATION || kind_ == LIGATURE_BREAK - || kind_ == NOBREAKDASH; + || kind_ == NOBREAKDASH + || kind_ == PHRASE_LYX || kind_ == PHRASE_LATEX + || kind_ == PHRASE_TEX || kind_ == PHRASE_LATEX2E; } @@ -586,8 +657,8 @@ bool InsetSpecialChar::isLineSeparator() const // Paragraph::stripLeadingSpaces nukes the characters which // have this property. I leave the code here, since it should // eventually be made to work. (JMarc 20020327) - return kind_ == HYPHENATION || kind_ == MENU_SEPARATOR - || kind_ == SLASH; + return kind_ == HYPHENATION || kind_ == ALLOWBREAK + || kind_ == MENU_SEPARATOR || kind_ == SLASH; #else return false; #endif