2 * \file InsetQuotes.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Jean-Marc Lasgouttes
7 * \author Jürgen Spitzmüller
9 * Full author contact details are available in file CREDITS.
14 #include "InsetQuotes.h"
17 #include "BufferParams.h"
18 #include "BufferView.h"
20 #include "Dimension.h"
22 #include "FuncStatus.h"
23 #include "FuncRequest.h"
25 #include "LaTeXFeatures.h"
28 #include "MetricsInfo.h"
29 #include "OutputParams.h"
30 #include "output_xhtml.h"
31 #include "Paragraph.h"
32 #include "ParIterator.h"
33 #include "texstream.h"
35 #include "frontends/FontMetrics.h"
36 #include "frontends/Painter.h"
38 #include "support/debug.h"
39 #include "support/docstring.h"
40 #include "support/docstream.h"
41 #include "support/gettext.h"
42 #include "support/lstrings.h"
47 using namespace lyx::support;
53 /* codes used to read/write quotes to LyX files
55 * e ``english'' (`inner quotation')
56 * s ''swedish'' ('inner quotation')
57 * g ,,german`` (,inner quotation`)
58 * p ,,polish'' (,inner quotation')
59 * c <<swiss>> (<inner quotation>)
60 * a >>danish<< (>inner quotation<)
61 * q "plain" ('inner quotation')
62 * b `british' (``inner quotation'')
63 * w >>swedishg>> ('inner quotation') ["g" = Guillemets]
64 * f <<french>> (``inner quotation'')
65 * i <<frenchin>> (<<inner quotation>>) ["in" = Imprimerie Nationale]
66 * r <<russian>> (,,inner quotation``)
69 char const * const style_char = "esgpcaqbwfir";
70 char const * const side_char = "lr" ;
71 char const * const level_char = "sd";
76 /////////////////////////////////////////////////////////////////////
80 ///////////////////////////////////////////////////////////////////////
82 InsetQuotesParams quoteparams;
85 int InsetQuotesParams::stylescount() const
87 return strlen(style_char);
91 char_type InsetQuotesParams::getQuoteChar(QuoteStyle const & style, QuoteLevel const & level,
92 QuoteSide const & side) const
94 // main opening quotation mark
95 char_type left_primary;
96 // main closing quotation mark
97 char_type right_primary;
98 // secondary (inner, 'single') opening quotation mark
99 char_type left_secondary;
100 // secondary (inner, 'single') closing quotation mark
101 char_type right_secondary;
104 case EnglishQuotes: {
105 left_primary = 0x201c; // ``
106 right_primary = 0x201d; // ''
107 left_secondary = 0x2018; // `
108 right_secondary = 0x2019; // '
111 case SwedishQuotes: {
112 left_primary = 0x201d; // ''
113 right_primary = 0x201d; // ''
114 left_secondary = 0x2019; // '
115 right_secondary = 0x2019; // '
119 left_primary = 0x201e; // ,,
120 right_primary = 0x201c; // ``
121 left_secondary = 0x201a; // ,
122 right_secondary = 0x2018; // `
126 left_primary = 0x201e; // ,,
127 right_primary = 0x201d; // ''
128 left_secondary = 0x201a; // ,
129 right_secondary = 0x2019; // '
133 left_primary = 0x00ab; // <<
134 right_primary = 0x00bb; // >>
135 left_secondary = 0x2039; // <
136 right_secondary = 0x203a; // >
140 left_primary = 0x00bb; // >>
141 right_primary = 0x00ab; // <<
142 left_secondary = 0x203a; // >
143 right_secondary = 0x2039; // <
147 left_primary = 0x0022; // "
148 right_primary = 0x0022; // "
149 left_secondary = 0x0027; // '
150 right_secondary = 0x0027; // '
153 case BritishQuotes: {
154 left_primary = 0x2018; // `
155 right_primary = 0x2019; // '
156 left_secondary = 0x201c; // ``
157 right_secondary = 0x201d; // ''
160 case SwedishGQuotes: {
161 left_primary = 0x00bb; // >>
162 right_primary = 0x00bb; // >>
163 left_secondary = 0x2019; // '
164 right_secondary = 0x2019; // '
168 left_primary = 0x00ab; // <<
169 right_primary = 0x00bb; // >>
170 left_secondary = 0x201c; // ``
171 right_secondary = 0x201d; // ''
174 case FrenchINQuotes:{
175 left_primary = 0x00ab; // <<
176 right_primary = 0x00bb; // >>
177 left_secondary = 0x00ab; // <<
178 right_secondary = 0x00bb; // >>
182 left_primary = 0x00ab; // <<
183 right_primary = 0x00bb; // >>
184 left_secondary = 0x201e; // ,,
185 right_secondary = 0x201c; // ``
190 left_primary = 0x003f; // ?
191 right_primary = 0x003f; // ?
192 left_secondary = 0x003f; // ?
193 right_secondary = 0x003f; // ?
198 case SecondaryQuotes:
199 return (side == OpeningQuote) ? left_secondary : right_secondary;
201 return (side == OpeningQuote) ? left_primary : right_primary;
211 docstring InsetQuotesParams::getLaTeXQuote(char_type c, string const & op) const
220 res = "\\quotesinglbase";
225 res = "\\textquoteleft";
232 res = "\\textquoteright";
241 res = "\\guilsinglleft";
248 res = "\\guilsinglright";
251 case 0x0027: {// ' (plain)
252 res = "\\textquotesingle";
258 else if (op == "babel")
261 res = "\\quotedblbase";
266 res = "\\textquotedblleft";
273 res = "\\textquotedblright";
281 else if (op == "babel")
284 res = "\\guillemotleft";
290 else if (op == "babel")
293 res = "\\guillemotright";
297 res = "\\textquotedbl";
304 return from_ascii(res);
308 docstring InsetQuotesParams::getHTMLQuote(char_type c) const
328 case 0x0027: // ' (plain)
353 return from_ascii(res);
357 map<string, docstring> InsetQuotesParams::getTypes() const
359 map<string, docstring> res;
367 // get all quote types
368 for (sty = 0; sty < stylescount(); ++sty) {
369 style = QuoteStyle(sty);
370 for (sid = 0; sid < 2; ++sid) {
371 side = QuoteSide(sid);
372 for (lev = 0; lev < 2; ++lev) {
373 type += style_char[style];
374 type += side_char[sid];
375 level = QuoteLevel(lev);
376 type += level_char[lev];
377 res[type] = docstring(1, getQuoteChar(style, level, side));
386 docstring const InsetQuotesParams::getGuiLabel(QuoteStyle const & qs)
388 return bformat(_("%1$souter%2$s and %3$sinner%4$s[[quotation marks]]"),
389 docstring(1, quoteparams.getQuoteChar(qs, PrimaryQuotes, OpeningQuote)),
390 docstring(1, quoteparams.getQuoteChar(qs, PrimaryQuotes, ClosingQuote)),
391 docstring(1, quoteparams.getQuoteChar(qs, SecondaryQuotes, OpeningQuote)),
392 docstring(1, quoteparams.getQuoteChar(qs, SecondaryQuotes, ClosingQuote))
397 /////////////////////////////////////////////////////////////////////
401 ///////////////////////////////////////////////////////////////////////
403 InsetQuotes::InsetQuotes(Buffer * buf, string const & str) : Inset(buf)
409 InsetQuotes::InsetQuotes(Buffer * buf, char_type c, InsetQuotesParams::QuoteLevel level,
410 string const & side, string const & style)
411 : Inset(buf), level_(level), pass_thru_(false)
414 style_ = style.empty() ? buf->params().quotes_style : getStyle(style);
415 fontenc_ = (buf->params().fontenc == "global")
416 ? lyxrc.fontenc : buf->params().fontenc;
418 style_ = style.empty() ? InsetQuotesParams::EnglishQuotes : getStyle(style);
419 fontenc_ = lyxrc.fontenc;
422 if (side == "left" || side == "opening")
423 side_ = InsetQuotesParams::OpeningQuote;
424 else if (side == "right" || side == "closing")
425 side_ = InsetQuotesParams::ClosingQuote;
431 docstring InsetQuotes::layoutName() const
433 return from_ascii("Quotes");
437 void InsetQuotes::setSide(char_type c)
439 // Decide whether opening or closing quote
444 side_ = InsetQuotesParams::OpeningQuote;// opening quote
447 side_ = InsetQuotesParams::ClosingQuote;// closing quote
452 void InsetQuotes::parseString(string const & s, bool const allow_wildcards)
455 if (str.length() != 3) {
456 lyxerr << "ERROR (InsetQuotes::InsetQuotes):"
457 " bad string length." << endl;
463 // '.' wildcard means: keep current stylee
464 if (!allow_wildcards || str[0] != '.') {
465 for (i = 0; i < quoteparams.stylescount(); ++i) {
466 if (str[0] == style_char[i]) {
467 style_ = InsetQuotesParams::QuoteStyle(i);
471 if (i >= quoteparams.stylescount()) {
472 LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
473 " bad style specification.");
474 style_ = InsetQuotesParams::EnglishQuotes;
478 // '.' wildcard means: keep current side
479 if (!allow_wildcards || str[1] != '.') {
480 for (i = 0; i < 2; ++i) {
481 if (str[1] == side_char[i]) {
482 side_ = InsetQuotesParams::QuoteSide(i);
487 LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
488 " bad side specification.");
489 side_ = InsetQuotesParams::OpeningQuote;
493 // '.' wildcard means: keep current level
494 if (!allow_wildcards || str[2] != '.') {
495 for (i = 0; i < 2; ++i) {
496 if (str[2] == level_char[i]) {
497 level_ = InsetQuotesParams::QuoteLevel(i);
502 LYXERR0("ERROR (InsetQuotes::InsetQuotes):"
503 " bad level specification.");
504 level_ = InsetQuotesParams::PrimaryQuotes;
510 InsetQuotesParams::QuoteStyle InsetQuotes::getStyle(string const & s)
512 InsetQuotesParams::QuoteStyle qs = InsetQuotesParams::EnglishQuotes;
515 qs = InsetQuotesParams::EnglishQuotes;
516 else if (s == "swedish")
517 qs = InsetQuotesParams::SwedishQuotes;
518 else if (s == "german")
519 qs = InsetQuotesParams::GermanQuotes;
520 else if (s == "polish")
521 qs = InsetQuotesParams::PolishQuotes;
522 else if (s == "swiss")
523 qs = InsetQuotesParams::SwissQuotes;
524 else if (s == "danish")
525 qs = InsetQuotesParams::DanishQuotes;
526 else if (s == "plain")
527 qs = InsetQuotesParams::PlainQuotes;
528 else if (s == "british")
529 qs = InsetQuotesParams::BritishQuotes;
530 else if (s == "swedishg")
531 qs = InsetQuotesParams::SwedishGQuotes;
532 else if (s == "french")
533 qs = InsetQuotesParams::FrenchQuotes;
534 else if (s == "frenchin")
535 qs = InsetQuotesParams::FrenchINQuotes;
541 docstring InsetQuotes::displayString() const
543 // In PassThru, we use straight quotes
545 return (level_ == InsetQuotesParams::PrimaryQuotes) ?
546 from_ascii("\"") : from_ascii("'");
548 docstring retdisp = docstring(1, quoteparams.getQuoteChar(style_, level_, side_));
550 // in French, thin spaces are added inside double guillemets
551 if (prefixIs(context_lang_, "fr")
552 && level_ == InsetQuotesParams::PrimaryQuotes
553 && (style_ == InsetQuotesParams::SwissQuotes
554 || style_ == InsetQuotesParams::FrenchQuotes
555 || style_ == InsetQuotesParams::FrenchINQuotes)) {
556 // THIN SPACE (U+2009)
557 char_type const thin_space = 0x2009;
558 if (side_ == InsetQuotesParams::OpeningQuote)
559 retdisp += thin_space;
561 retdisp = thin_space + retdisp;
568 void InsetQuotes::metrics(MetricsInfo & mi, Dimension & dim) const
570 FontInfo & font = mi.base.font;
571 frontend::FontMetrics const & fm = theFontMetrics(font);
572 dim.asc = fm.maxAscent();
573 dim.des = fm.maxDescent();
574 dim.wid = fm.width(displayString());
578 void InsetQuotes::draw(PainterInfo & pi, int x, int y) const
580 FontInfo font = pi.base.font;
581 font.setPaintColor(pi.textColor(font.realColor()));
582 pi.pain.text(x, y, displayString(), font);
586 string InsetQuotes::getType() const
589 text += style_char[style_];
590 text += side_char[side_];
591 text += level_char[level_];
596 void InsetQuotes::write(ostream & os) const
598 os << "Quotes " << getType();
602 void InsetQuotes::read(Lexer & lex)
604 lex.setContext("InsetQuotes::read");
606 parseString(lex.getString());
607 lex >> "\\end_inset";
611 void InsetQuotes::doDispatch(Cursor & cur, FuncRequest & cmd)
613 switch (cmd.action()) {
614 case LFUN_INSET_MODIFY: {
615 string const first_arg = cmd.getArg(0);
616 bool const change_type = first_arg == "changetype";
619 // this will not be handled higher up
623 cur.recordUndoInset(this);
624 parseString(cmd.getArg(1), true);
625 cur.buffer()->updateBuffer();
629 Inset::doDispatch(cur, cmd);
635 bool InsetQuotes::getStatus(Cursor & cur, FuncRequest const & cmd,
636 FuncStatus & flag) const
638 switch (cmd.action()) {
640 case LFUN_INSET_MODIFY: {
641 string const first_arg = cmd.getArg(0);
642 if (first_arg == "changetype") {
643 string const type = cmd.getArg(1);
644 flag.setOnOff(type == getType());
645 flag.setEnabled(!pass_thru_);
648 return Inset::getStatus(cur, cmd, flag);
652 return Inset::getStatus(cur, cmd, flag);
657 void InsetQuotes::latex(otexstream & os, OutputParams const & runparams) const
659 char_type quotechar = quoteparams.getQuoteChar(style_, level_, side_);
662 // In pass-thru context, we output plain quotes
663 if (runparams.pass_thru)
664 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ? from_ascii("\"") : from_ascii("'");
665 else if (style_ == InsetQuotesParams::PlainQuotes && runparams.isFullUnicode()) {
666 // For XeTeX and LuaTeX,we need to disable mapping to get straight
667 // quotes. We define our own commands that do this
668 qstr = (level_ == InsetQuotesParams::PrimaryQuotes) ?
669 from_ascii("\\textquotedblplain") : from_ascii("\\textquotesingleplain");
671 else if (runparams.use_polyglossia) {
672 // For polyglossia, we directly output the respective unicode chars
673 // (spacing and kerning is then handled respectively)
674 qstr = docstring(1, quotechar);
676 else if (style_ == InsetQuotesParams::FrenchQuotes
677 && level_ == InsetQuotesParams::PrimaryQuotes
678 && prefixIs(runparams.local_font->language()->code(), "fr")) {
679 // Specific guillemets of French babel
680 // including correct French spacing
681 if (side_ == InsetQuotesParams::OpeningQuote)
682 qstr = from_ascii("\\og");
684 qstr = from_ascii("\\fg");
685 } else if (fontenc_ == "T1"
686 && !runparams.local_font->language()->internalFontEncoding()) {
687 // Quotation marks for T1 font encoding
689 qstr = quoteparams.getLaTeXQuote(quotechar, "t1");
690 } else if (runparams.local_font->language()->internalFontEncoding()) {
691 // Quotation marks for internal font encodings
692 // (ligatures not featured)
693 qstr = quoteparams.getLaTeXQuote(quotechar, "int");
694 #ifdef DO_USE_DEFAULT_LANGUAGE
695 } else if (doclang == "default") {
697 } else if (!runparams.use_babel || runparams.isFullUnicode()) {
699 // Standard quotation mark macros
700 // These are also used by babel
701 // without fontenc (XeTeX/LuaTeX)
702 qstr = quoteparams.getLaTeXQuote(quotechar, "ot1");
704 // Babel shorthand quotation marks (for T1/OT1)
705 qstr = quoteparams.getLaTeXQuote(quotechar, "babel");
708 if (!runparams.pass_thru) {
709 // Guard against unwanted ligatures with preceding text
710 char_type const lastchar = os.lastChar();
711 // !` ?` => !{}` ?{}`
712 if (prefixIs(qstr, from_ascii("`"))
713 && (lastchar == '!' || lastchar == '?'))
715 // ``` ''' ,,, <<< >>>
716 // => `{}`` '{}'' ,{},, <{}<< >{}>>
717 if (contains(from_ascii(",'`<>"), lastchar)
718 && prefixIs(qstr, lastchar))
724 if (prefixIs(qstr, from_ascii("\\")))
725 // properly terminate the command depending on the context
730 int InsetQuotes::plaintext(odocstringstream & os,
731 OutputParams const &, size_t) const
733 docstring const str = displayString();
739 docstring InsetQuotes::getQuoteEntity() const {
740 docstring res = quoteparams.getHTMLQuote(quoteparams.getQuoteChar(style_, level_, side_));
741 // in French, thin spaces are added inside double guillemets
742 if (prefixIs(context_lang_, "fr")
743 && level_ == InsetQuotesParams::PrimaryQuotes
744 && style_ == InsetQuotesParams::FrenchQuotes) {
745 // THIN SPACE (U+2009)
746 docstring const thin_space = from_ascii(" ");
747 if (side_ == InsetQuotesParams::OpeningQuote)
750 res = thin_space + res;
756 int InsetQuotes::docbook(odocstream & os, OutputParams const &) const
758 os << getQuoteEntity();
763 docstring InsetQuotes::xhtml(XHTMLStream & xs, OutputParams const &) const
765 xs << XHTMLStream::ESCAPE_NONE << getQuoteEntity();
770 void InsetQuotes::toString(odocstream & os) const
772 os << displayString();
776 void InsetQuotes::forOutliner(docstring & os, size_t const, bool const) const
778 os += displayString();
782 void InsetQuotes::updateBuffer(ParIterator const & it, UpdateType /* utype*/)
784 BufferParams const & bp = buffer().masterBuffer()->params();
785 pass_thru_ = it.paragraph().isPassThru();
786 context_lang_ = it.paragraph().getFontSettings(bp, it.pos()).language()->code();
787 fontenc_ = (bp.fontenc == "global") ? lyxrc.fontenc : bp.fontenc;
791 void InsetQuotes::validate(LaTeXFeatures & features) const
793 char_type type = quoteparams.getQuoteChar(style_, level_, side_);
795 // Handle characters that are not natively supported by
796 // specific font encodings (we roll our own definitions)
797 #ifdef DO_USE_DEFAULT_LANGUAGE
798 if (features.bufferParams().language->lang() == "default"
800 if (!features.useBabel()
802 && !features.runparams().isFullUnicode() && fontenc_ != "T1") {
805 features.require("quotesinglbase");
808 features.require("guilsinglleft");
811 features.require("guilsinglright");
814 features.require("quotedblbase");
817 features.require("guillemotleft");
820 features.require("guillemotright");
826 // Handle straight quotation marks. These need special care
827 // in most output formats
830 if (features.runparams().isFullUnicode())
831 features.require("textquotesinglep");
833 features.require("textcomp");
837 if (features.runparams().isFullUnicode())
838 features.require("textquotedblp");
839 else if (fontenc_ != "T1")
840 features.require("textquotedbl");
849 string InsetQuotes::contextMenuName() const
851 return "context-quote";