]> git.lyx.org Git - lyx.git/blob - src/insets/InsetIPAMacro.cpp
InsetIPAMacro: implement the XHTML conversion, refactor plaintext and DocBook to...
[lyx.git] / src / insets / InsetIPAMacro.cpp
1 /**
2  * \file InsetIPAMacro.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Jürgen Spitzmüller
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "InsetIPAMacro.h"
14
15 #include "Buffer.h"
16 #include "BufferParams.h"
17 #include "Dimension.h"
18 #include "Encoding.h"
19 #include "FontInfo.h"
20 #include "FuncRequest.h"
21 #include "FuncStatus.h"
22 #include "InsetLayout.h"
23 #include "LaTeXFeatures.h"
24 #include "Lexer.h"
25 #include "MetricsInfo.h"
26 #include "xml.h"
27 #include "texstream.h"
28
29 #include "frontends/FontMetrics.h"
30 #include "frontends/Painter.h"
31
32 #include "support/debug.h"
33 #include "support/docstream.h"
34 #include "support/gettext.h"
35 #include "support/Translator.h"
36
37 using namespace std;
38
39 namespace lyx {
40
41 namespace {
42
43 typedef Translator<string, InsetIPADecoParams::Type> IPADecoTranslator;
44 typedef Translator<docstring, InsetIPADecoParams::Type> IPADecoTranslatorLoc;
45
46 IPADecoTranslator const init_ipadecotranslator()
47 {
48         IPADecoTranslator translator("toptiebar", InsetIPADecoParams::Toptiebar);
49         translator.addPair("bottomtiebar", InsetIPADecoParams::Bottomtiebar);
50         return translator;
51 }
52
53
54 IPADecoTranslatorLoc const init_ipadecotranslator_loc()
55 {
56         IPADecoTranslatorLoc translator(_("Top tie bar"), InsetIPADecoParams::Toptiebar);
57         translator.addPair(_("Bottom tie bar"), InsetIPADecoParams::Bottomtiebar);
58         return translator;
59 }
60
61
62 IPADecoTranslator const & ipadecotranslator()
63 {
64         static IPADecoTranslator const decotranslator =
65                         init_ipadecotranslator();
66         return decotranslator;
67 }
68
69
70 IPADecoTranslatorLoc const & ipadecotranslator_loc()
71 {
72         static IPADecoTranslatorLoc const translator =
73             init_ipadecotranslator_loc();
74         return translator;
75 }
76
77
78 typedef Translator<string, InsetIPAChar::Kind> IPACharTranslator;
79
80 IPACharTranslator const init_ipachartranslator()
81 {
82         IPACharTranslator translator("\\tone{51}", InsetIPAChar::TONE_FALLING);
83         translator.addPair("\\tone{15}", InsetIPAChar::TONE_RISING);
84         translator.addPair("\\tone{45}", InsetIPAChar::TONE_HIGH_RISING);
85         translator.addPair("\\tone{12}", InsetIPAChar::TONE_LOW_RISING);
86         translator.addPair("\\tone{454}", InsetIPAChar::TONE_HIGH_RISING_FALLING);
87         return translator;
88 }
89
90
91 IPACharTranslator const & ipachartranslator()
92 {
93         static IPACharTranslator const chartranslator =
94             init_ipachartranslator();
95         return chartranslator;
96 }
97
98 } // namespace
99
100
101 InsetIPADecoParams::InsetIPADecoParams()
102         : type(Bottomtiebar)
103 {}
104
105
106 void InsetIPADecoParams::write(ostream & os) const
107 {
108         string const label = ipadecotranslator().find(type);
109         os << "IPADeco " << label << "\n";
110 }
111
112
113 void InsetIPADecoParams::read(Lexer & lex)
114 {
115         string label;
116         lex >> label;
117         if (lex)
118                 type = ipadecotranslator().find(label);
119 }
120
121
122 /////////////////////////////////////////////////////////////////////
123 //
124 // InsetIPADeco
125 //
126 /////////////////////////////////////////////////////////////////////
127
128 InsetIPADeco::InsetIPADeco(Buffer * buf, string const & label)
129         : InsetCollapsible(buf)
130 {
131         setDrawFrame(true);
132         setFrameColor(Color_insetframe);
133         params_.type = ipadecotranslator().find(label);
134 }
135
136
137 InsetIPADeco::~InsetIPADeco()
138 {}
139
140
141 docstring InsetIPADeco::layoutName() const
142 {
143         return from_ascii("IPADeco:" + ipadecotranslator().find(params_.type));
144 }
145
146
147 void InsetIPADeco::metrics(MetricsInfo & mi, Dimension & dim) const
148 {
149         InsetText::metrics(mi, dim);
150
151         if (params_.type == InsetIPADecoParams::Toptiebar) {
152                 // consider width of the inset label
153                 FontInfo font(getLayout().labelfont());
154                 font.realize(sane_font);
155                 font.decSize();
156                 font.decSize();
157                 int w = 0;
158                 int a = 0;
159                 int d = 0;
160                 docstring const label(1, char_type(0x2040));
161                 theFontMetrics(font).rectText(label, w, a, d);
162                 dim.asc += int(a * 0.5);
163         }
164         if (params_.type == InsetIPADecoParams::Bottomtiebar) {
165                 // consider width of the inset label
166                 FontInfo font(getLayout().labelfont());
167                 font.realize(sane_font);
168                 font.decSize();
169                 font.decSize();
170                 int w = 0;
171                 int a = 0;
172                 int d = 0;
173                 docstring const label(1, char_type(0x203f));
174                 theFontMetrics(font).rectText(label, w, a, d);
175                 dim.des += int(d * 1.5);
176         }
177 }
178
179
180 void InsetIPADeco::draw(PainterInfo & pi, int x, int y) const
181 {
182         // draw the text
183         InsetCollapsible::draw(pi, x, y);
184
185         // draw the inset marker
186         drawMarkers(pi, x, y);
187
188         Dimension const dim = dimension(*pi.base.bv);
189
190         if (params_.type == InsetIPADecoParams::Toptiebar) {
191                 FontInfo font(getLayout().labelfont());
192                 font.realize(sane_font);
193                 font.decSize();
194                 font.decSize();
195                 int w = 0;
196                 int a = 0;
197                 int d = 0;
198                 int asc = dim.ascent();
199                 docstring const label(1, char_type(0x2040));
200                 theFontMetrics(font).rectText(label, w, a, d);
201                 int const ww = max(dim.wid, w);
202                 pi.pain.rectText(x + (ww - w) / 2, y - int(asc / 2.5),
203                         label, font, Color_none, Color_none);
204         }
205
206         if (params_.type == InsetIPADecoParams::Bottomtiebar) {
207                 FontInfo font(getLayout().labelfont());
208                 font.realize(sane_font);
209                 font.decSize();
210                 font.decSize();
211                 int w = 0;
212                 int a = 0;
213                 int d = 0;
214                 int desc = dim.descent();
215                 docstring const label(1, char_type(0x203f));
216                 theFontMetrics(font).rectText(label, w, a, d);
217                 int const ww = max(dim.wid, w);
218                 pi.pain.rectText(x + (ww - w) / 2, y + int(desc / 1.5),
219                         label, font, Color_none, Color_none);
220         }
221 }
222
223
224 void InsetIPADeco::write(ostream & os) const
225 {
226         params_.write(os);
227         InsetCollapsible::write(os);
228 }
229
230
231 void InsetIPADeco::read(Lexer & lex)
232 {
233         params_.read(lex);
234         InsetCollapsible::read(lex);
235 }
236
237
238 void InsetIPADeco::doDispatch(Cursor & cur, FuncRequest & cmd)
239 {
240         switch (cmd.action()) {
241         case LFUN_QUOTE_INSERT: {
242                 FuncRequest fr(LFUN_SELF_INSERT, "\"");
243                 InsetText::doDispatch(cur, fr);
244                 break;
245         }
246         default:
247                 InsetText::doDispatch(cur, cmd);
248                 break;
249         }
250 }
251
252
253 bool InsetIPADeco::getStatus(Cursor & cur, FuncRequest const & cmd,
254                 FuncStatus & flag) const
255 {
256         switch (cmd.action()) {
257         case LFUN_SCRIPT_INSERT: {
258                 if (cmd.argument() == "subscript") {
259                         flag.setEnabled(false);
260                         return true;
261                 }
262                 break;
263         }
264         case LFUN_IN_IPA:
265                 flag.setEnabled(true);
266                 return true;
267                 break;
268         default:
269                 break;
270         }
271         return InsetText::getStatus(cur, cmd, flag);
272 }
273
274
275 void InsetIPADeco::latex(otexstream & os, OutputParams const & runparams) const
276 {
277         if (params_.type == InsetIPADecoParams::Toptiebar)
278                 os << "\\texttoptiebar{";
279         else if (params_.type == InsetIPADecoParams::Bottomtiebar)
280                 os << "\\textbottomtiebar{";
281         InsetCollapsible::latex(os, runparams);
282         os << "}";
283 }
284
285
286 namespace {
287 std::pair<docstring, docstring> splitPlainTextInHalves(
288                 const InsetIPADeco * inset, OutputParams const & runparams,
289                 size_t max_length = INT_MAX)
290 {
291         odocstringstream ods;
292         int h = inset->InsetCollapsible::plaintext(ods, runparams, max_length) / 2;
293         docstring result = ods.str();
294         docstring const before = result.substr(0, h);
295         docstring const after = result.substr(h, result.size());
296         return {before, after};
297 }
298 }
299
300
301 int InsetIPADeco::plaintext(odocstringstream & os,
302                             OutputParams const & runparams, size_t max_length) const
303 {
304         docstring before;
305         docstring after;
306         tie(before, after) = splitPlainTextInHalves(this, runparams, max_length);
307
308         if (params_.type == InsetIPADecoParams::Toptiebar) {
309                 os << before;
310                 os.put(0x0361);
311                 os << after;
312         }
313         else if (params_.type == InsetIPADecoParams::Bottomtiebar) {
314                 os << before;
315                 os.put(0x035c);
316                 os << after;
317         }
318         return before.size() + after.size();
319 }
320
321
322 void InsetIPADeco::docbook(XMLStream & xs, OutputParams const & runparams) const
323 {
324         // The special combining character must be put in the middle, between the two other characters.
325         // It will not work if there is anything else than two pure characters, so going back to plaintext.
326         docstring before;
327         docstring after;
328         tie(before, after) = splitPlainTextInHalves(this, runparams);
329
330         xs << XMLStream::ESCAPE_NONE << before;
331         if (params_.type == InsetIPADecoParams::Toptiebar)
332                 xs << XMLStream::ESCAPE_NONE << "&#x0361;";
333         else if (params_.type == InsetIPADecoParams::Bottomtiebar)
334                 xs << XMLStream::ESCAPE_NONE << "&#x035c;";
335         xs << XMLStream::ESCAPE_NONE << after;
336 }
337
338
339 docstring InsetIPADeco::xhtml(XMLStream & xs, OutputParams const & runparams) const
340 {
341         // The DocBook encoding for this inset has no DocBook tag, but sheer XML (relying on a plaintext
342         // transformation of the inset).
343         docbook(xs, runparams);
344         return docstring();
345 }
346
347
348 docstring InsetIPADeco::toolTip(BufferView const &, int, int) const
349 {
350         return ipadecotranslator_loc().find(params_.type);
351 }
352
353
354 string InsetIPADeco::params2string(InsetIPADecoParams const & params)
355 {
356         ostringstream data;
357         data << "IPADeco" << ' ';
358         params.write(data);
359         return data.str();
360 }
361
362
363 void InsetIPADeco::string2params(string const & in, InsetIPADecoParams & params)
364 {
365         params = InsetIPADecoParams();
366
367         if (in.empty())
368                 return;
369
370         istringstream data(in);
371         Lexer lex;
372         lex.setStream(data);
373         lex.setContext("InsetIPADeco::string2params");
374         lex >> "IPADeco" >> "toptiebar";
375
376         params.read(lex);
377 }
378
379
380 void InsetIPADeco::validate(LaTeXFeatures & features) const
381 {
382         features.require("tipa");
383         InsetText::validate(features);
384 }
385
386
387 bool InsetIPADeco::insetAllowed(InsetCode code) const
388 {
389         switch (code) {
390         // code that is allowed
391         case ERT_CODE:
392         case IPACHAR_CODE:
393         case SCRIPT_CODE:
394                 return true;
395         default:
396                 return false;
397         }
398 }
399
400 /////////////////////////////////////////////////////////////////////////
401 //
402 // InsetIPAChar
403 //
404 /////////////////////////////////////////////////////////////////////////
405
406
407 InsetIPAChar::InsetIPAChar(Kind k)
408         : Inset(nullptr), kind_(k)
409 {}
410
411
412 InsetIPAChar::Kind InsetIPAChar::kind() const
413 {
414         return kind_;
415 }
416
417
418 void InsetIPAChar::metrics(MetricsInfo & mi, Dimension & dim) const
419 {
420         frontend::FontMetrics const & fm =
421                 theFontMetrics(mi.base.font);
422         dim.asc = fm.maxAscent();
423         dim.des = fm.maxDescent();
424
425         string s;
426         switch (kind_) {
427                 case TONE_FALLING:
428                 case TONE_RISING:
429                 case TONE_HIGH_RISING:
430                 case TONE_LOW_RISING:
431                 case TONE_HIGH_RISING_FALLING:
432                         s = "_";
433                         break;
434         }
435         docstring ds(s.begin(), s.end());
436         dim.wid = fm.width(ds);
437 }
438
439
440 void InsetIPAChar::draw(PainterInfo & pi, int x, int y) const
441 {
442         FontInfo font = pi.base.font;
443         frontend::FontMetrics const & fm =
444                         theFontMetrics(font);
445
446         switch (kind_) {
447         case TONE_FALLING:
448         {
449                 int w = fm.width(char_type('-'));
450                 int h = fm.ascent(char_type('M'));
451                 int x2 = x + w;
452                 int y2 = y - h;
453
454                 pi.pain.line(x2, y2, x2, y, Color_foreground);
455                 pi.pain.line(x2, y, x, y2, Color_foreground);
456                 break;
457         }
458         case TONE_RISING:
459         {
460                 int w = fm.width(char_type('-'));
461                 int h = fm.ascent(char_type('M'));
462                 int x2 = x + w;
463                 int y2 = y - h;
464
465                 pi.pain.line(x2, y, x2, y2, Color_foreground);
466                 pi.pain.line(x2, y2, x, y, Color_foreground);
467                 break;
468         }
469         case TONE_HIGH_RISING:
470         {
471                 int w = fm.width(char_type('-'));
472                 int h = fm.ascent(char_type('M'));
473                 int x2 = x + w;
474                 int y2 = y - h;
475                 int y3 = y - int(h * 0.75);
476
477                 pi.pain.line(x2, y, x2, y2, Color_foreground);
478                 pi.pain.line(x2, y2, x, y3, Color_foreground);
479                 break;
480         }
481         case TONE_LOW_RISING:
482         {
483                 int w = fm.width(char_type('-'));
484                 int h = fm.ascent(char_type('M'));
485                 int x2 = x + w;
486                 int y2 = y - h;
487                 int y3 = y - int(h * 0.25);
488
489                 pi.pain.line(x2, y, x2, y2, Color_foreground);
490                 pi.pain.line(x2, y3, x, y, Color_foreground);
491                 break;
492         }
493         case TONE_HIGH_RISING_FALLING:
494         {
495                 int w = fm.width(char_type('-'));
496                 int h = fm.ascent(char_type('M'));
497                 int x2 = x + w;
498                 int y2 = y - h;
499                 int x3 = x + int(w * 0.5);
500                 int y3 = y - int(h * 0.75);
501
502                 pi.pain.line(x2, y, x2, y2, Color_foreground);
503                 pi.pain.line(x2, y3, x3, y2, Color_foreground);
504                 pi.pain.line(x3, y2, x, y3, Color_foreground);
505                 break;
506         }
507         }
508 }
509
510
511 void InsetIPAChar::write(ostream & os) const
512 {
513         string const command = ipachartranslator().find(kind_);
514         if (command.empty()) {
515                 LYXERR0("InsetIPAChar::write: Unknown type");
516                 return;
517         }
518         os << "\\IPAChar " << command << "\n";
519 }
520
521
522 void InsetIPAChar::read(Lexer & lex)
523 {
524         lex.next();
525         string const command = lex.getString();
526         kind_ = ipachartranslator().find(command);
527 }
528
529
530 void InsetIPAChar::latex(otexstream & os,
531                          OutputParams const &) const
532 {
533         string const command = ipachartranslator().find(kind_);
534         os << command;
535 }
536
537
538 int InsetIPAChar::plaintext(odocstringstream & os, OutputParams const &, size_t) const
539 {
540         switch (kind_) {
541         case TONE_FALLING:
542                 os.put(0x02e5);
543                 os.put(0x02e9);
544                 return 2;
545         case TONE_RISING:
546                 os.put(0x02e9);
547                 os.put(0x02e5);
548                 return 2;
549         case TONE_HIGH_RISING:
550                 os.put(0x02e7);
551                 os.put(0x02e5);
552                 return 2;
553         case TONE_LOW_RISING:
554                 os.put(0x02e9);
555                 os.put(0x02e7);
556                 return 2;
557         case TONE_HIGH_RISING_FALLING:
558                 os.put(0x02e8);
559                 os.put(0x02e5);
560                 os.put(0x02e8);
561                 return 3;
562         }
563         return 0;
564 }
565
566
567 namespace {
568 std::string ipaCharToXMLEntity(InsetIPAChar::Kind kind) {
569         switch (kind) {
570         case InsetIPAChar::Kind::TONE_FALLING:
571                 return "&#x2e5;&#x2e9;";
572         case InsetIPAChar::Kind::TONE_RISING:
573                 return "&#x2e9;&#x2e5;";
574         case InsetIPAChar::Kind::TONE_HIGH_RISING:
575                 return "&#x2e7;&#x2e5;";
576         case InsetIPAChar::Kind::TONE_LOW_RISING:
577                 return "&#x2e9;&#x2e7;";
578         case InsetIPAChar::Kind::TONE_HIGH_RISING_FALLING:
579                 return "&#x2e8;&#x2e5;&#x2e8;";
580         }
581         return "";
582 }
583 }
584
585
586 void InsetIPAChar::docbook(XMLStream & xs, OutputParams const &) const
587 {
588         xs << XMLStream::ESCAPE_NONE << from_ascii(ipaCharToXMLEntity(kind()));
589 }
590
591
592 docstring InsetIPAChar::xhtml(XMLStream & xs, OutputParams const &) const
593 {
594         xs << XMLStream::ESCAPE_NONE << from_ascii(ipaCharToXMLEntity(kind()));
595         return docstring();
596 }
597
598
599 void InsetIPAChar::toString(odocstream & os) const
600 {
601         odocstringstream ods;
602         plaintext(ods, OutputParams(0));
603         os << ods.str();
604 }
605
606
607 void InsetIPAChar::forOutliner(docstring & os, size_t const, bool const) const
608 {
609         odocstringstream ods;
610         plaintext(ods, OutputParams(0));
611         os += ods.str();
612 }
613
614
615 void InsetIPAChar::validate(LaTeXFeatures & features) const
616 {
617         switch (kind_) {
618         case TONE_FALLING:
619         case TONE_RISING:
620         case TONE_HIGH_RISING:
621         case TONE_LOW_RISING:
622         case TONE_HIGH_RISING_FALLING:
623                 features.require("tone");
624                 break;
625         default:
626                 break;
627         }
628 }
629
630
631 } // namespace lyx