]> git.lyx.org Git - features.git/blob - src/mathed/InsetMathScript.cpp
Rm dup "Float" in "Insert>Float>Figure Wrap Float"
[features.git] / src / mathed / InsetMathScript.cpp
1 /**
2  * \file InsetMathScript.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "InsetMathScript.h"
14
15 #include "InsetMathBrace.h"
16 #include "MathData.h"
17 #include "MathStream.h"
18 #include "MathSupport.h"
19
20 #include "BufferView.h"
21 #include "Cursor.h"
22 #include "LaTeXFeatures.h"
23 #include "MetricsInfo.h"
24
25 #include "support/debug.h"
26 #include "support/gettext.h"
27 #include "support/lassert.h"
28
29
30 using namespace std;
31
32 namespace lyx {
33
34
35 InsetMathScript::InsetMathScript(Buffer * buf)
36         : InsetMathNest(buf, 1), cell_1_is_up_(false)
37 {}
38
39
40 InsetMathScript::InsetMathScript(Buffer * buf, bool up)
41         : InsetMathNest(buf, 2), cell_1_is_up_(up)
42 {}
43
44
45 InsetMathScript::InsetMathScript(Buffer * buf, MathAtom const & at, bool up)
46         : InsetMathNest(buf, 2), cell_1_is_up_(up)
47 {
48         LATTEST(nargs() >= 1);
49         cell(0).push_back(at);
50 }
51
52
53 Inset * InsetMathScript::clone() const
54 {
55         return new InsetMathScript(*this);
56 }
57
58
59 InsetMathScript const * InsetMathScript::asScriptInset() const
60 {
61         return this;
62 }
63
64
65 InsetMathScript * InsetMathScript::asScriptInset()
66 {
67         return this;
68 }
69
70
71 MathData const & InsetMathScript::down() const
72 {
73         if (nargs() == 3)
74                 return cell(2);
75         LBUFERR(nargs() > 1);
76         return cell(1);
77 }
78
79
80 MathData & InsetMathScript::down()
81 {
82         if (nargs() == 3)
83                 return cell(2);
84         LBUFERR(nargs() > 1);
85         return cell(1);
86 }
87
88
89 MathData const & InsetMathScript::up() const
90 {
91         LBUFERR(nargs() > 1);
92         return cell(1);
93 }
94
95
96 MathData & InsetMathScript::up()
97 {
98         LBUFERR(nargs() > 1);
99         return cell(1);
100 }
101
102
103 void InsetMathScript::ensure(bool up)
104 {
105         if (nargs() == 1) {
106                 // just nucleus so far
107                 cells_.push_back(MathData());
108                 cell_1_is_up_ = up;
109         } else if (nargs() == 2 && !has(up)) {
110                 if (up) {
111                         cells_.push_back(cell(1));
112                         cell(1).clear();
113                 } else {
114                         cells_.push_back(MathData());
115                 }
116         }
117 }
118
119
120 MathData const & InsetMathScript::nuc() const
121 {
122         return cell(0);
123 }
124
125
126 MathData & InsetMathScript::nuc()
127 {
128         return cell(0);
129 }
130
131
132 int InsetMathScript::dy01(BufferView const & bv, int asc, int des, int what) const
133 {
134         int dasc = 0;
135         int slevel = 0;
136         bool isCharBox = !nuc().empty() ? isAlphaSymbol(nuc().back()) : false;
137         if (hasDown()) {
138                 Dimension const & dimdown = down().dimension(bv);
139                 dasc = dimdown.ascent();
140                 slevel = nuc().slevel();
141                 int ascdrop = dasc - slevel;
142                 int desdrop = isCharBox ? 0 : des + nuc().sshift();
143                 int mindes = nuc().mindes();
144                 des = max(desdrop, ascdrop);
145                 des = max(mindes, des);
146         }
147         if (hasUp()) {
148                 Dimension const & dimup = up().dimension(bv);
149                 int minasc = nuc().minasc();
150                 int ascdrop = isCharBox ? 0 : asc - up().mindes();
151                 int udes = dimup.descent();
152                 asc = udes + nuc().sshift();
153                 asc = max(ascdrop, asc);
154                 asc = max(minasc, asc);
155                 if (hasDown()) {
156                         int del = asc - udes - dasc;
157                         if (del + des <= 2) {
158                                 int newdes = 2 - del;
159                                 del = slevel - asc + udes;
160                                 if (del > 0) {
161                                         asc += del;
162                                         newdes -= del;
163                                 }
164                                 des = max(des, newdes);
165                         }
166                 }
167         }
168         return what ? asc : des;
169 }
170
171
172 int InsetMathScript::dy0(BufferView const & bv) const
173 {
174         int nd = ndes(bv);
175         if (!hasDown())
176                 return nd;
177         int des = down().dimension(bv).ascent();
178         if (has_limits_)
179                 des += nd + 2;
180         else {
181                 int na = nasc(bv);
182                 des = dy01(bv, na, nd, 0);
183         }
184         return des;
185 }
186
187
188 int InsetMathScript::dy1(BufferView const & bv) const
189 {
190         int na = nasc(bv);
191         if (!hasUp())
192                 return na;
193         int asc = up().dimension(bv).descent();
194         if (has_limits_)
195                 asc += na + 2;
196         else {
197                 int nd = ndes(bv);
198                 asc = dy01(bv, na, nd, 1);
199         }
200         asc = max(asc, 5);
201         return asc;
202 }
203
204
205 int InsetMathScript::dx0(BufferView const & bv) const
206 {
207         LASSERT(hasDown(), return 0);
208         Dimension const dim = dimension(bv);
209         return has_limits_ ? (dim.wid - down().dimension(bv).width()) / 2
210                 : nwid(bv) + min(nker(&bv), 0);
211 }
212
213
214 int InsetMathScript::dx1(BufferView const & bv) const
215 {
216         LASSERT(hasUp(), return 0);
217         Dimension const dim = dimension(bv);
218         return has_limits_ ? (dim.wid - up().dimension(bv).width()) / 2
219                 : nwid(bv) + max(nker(&bv), 0);
220 }
221
222
223 int InsetMathScript::dxx(BufferView const & bv) const
224 {
225         Dimension const dim = dimension(bv);
226         return has_limits_ ? (dim.wid - nwid(bv)) / 2  :  0;
227 }
228
229
230 int InsetMathScript::nwid(BufferView const & bv) const
231 {
232         return !nuc().empty() ? nuc().dimension(bv).width() : 2;
233 }
234
235
236 int InsetMathScript::nasc(BufferView const & bv) const
237 {
238         return !nuc().empty() ? nuc().dimension(bv).ascent() : 5;
239 }
240
241
242 int InsetMathScript::ndes(BufferView const & bv) const
243 {
244         return !nuc().empty() ? nuc().dimension(bv).descent() : 0;
245 }
246
247
248 int InsetMathScript::nker(BufferView const * bv) const
249 {
250         if (!nuc().empty())
251                 return nuc().kerning(bv);
252         return 0;
253 }
254
255
256 Limits InsetMathScript::limits() const
257 {
258         if (nuc().empty())
259                 return AUTO_LIMITS;
260         else
261                 // only the limits status of the last element counts
262                 return nuc().back()->limits();
263 }
264
265
266 void InsetMathScript::limits(Limits lim)
267 {
268         if (!nuc().empty())
269                 nuc().back()->limits(lim);
270 }
271
272
273 MathClass InsetMathScript::mathClass() const
274 {
275         // FIXME: this is a hack, since the class will not be correct if
276         // the nucleus has several elements or if the last element is a math macro
277         // or a macro argument proxy.
278         // The correct implementation would require to linearize the nucleus.
279         if (nuc().empty())
280                 return MC_ORD;
281         else {
282                 // return the class of last element since this is the one that counts.
283                 MathClass mc = nuc().back()->mathClass();
284                 return (mc == MC_UNKNOWN) ? MC_ORD : mc;
285         }
286 }
287
288
289 void InsetMathScript::metrics(MetricsInfo & mi, Dimension & dim) const
290 {
291         // we store this, because it is much easier
292         has_limits_ = hasLimits(mi.base.font.style());
293
294         // Compute metrics of the available cells
295         Dimension dim0;
296         Dimension dim1;
297         Dimension dim2;
298         {
299                 // Keeps the changers local
300                 Changer dummy2 = mi.base.changeEnsureMath();
301                 cell(0).metrics(mi, dim0);
302                 Changer dummy = mi.base.changeScript();
303                 if (nargs() > 1)
304                         cell(1).metrics(mi, dim1, !has_limits_);
305                 if (nargs() > 2)
306                         cell(2).metrics(mi, dim2, !has_limits_);
307         }
308
309         dim.wid = 0;
310         BufferView & bv = *mi.base.bv;
311         // FIXME: data copying... not very efficient.
312         Dimension dimup;
313         Dimension dimdown;
314         if (hasUp())
315                 dimup = up().dimension(bv);
316         if (hasDown())
317                 dimdown = down().dimension(bv);
318
319         if (has_limits_) {
320                 dim.wid = nwid(bv);
321                 if (hasUp())
322                         dim.wid = max(dim.wid, dimup.width());
323                 if (hasDown())
324                         dim.wid = max(dim.wid, dimdown.width());
325         } else {
326                 if (hasUp())
327                         dim.wid = max(dim.wid, max(nker(mi.base.bv), 0) + dimup.width());
328                 if (hasDown())
329                         dim.wid = max(dim.wid, min(nker(mi.base.bv), 0) + dimdown.width());
330                 dim.wid += nwid(bv);
331         }
332         int na = nasc(bv);
333         if (hasUp()) {
334                 int asc = dy1(bv) + dimup.ascent();
335                 dim.asc = max(na, asc);
336         } else
337                 dim.asc = na;
338         int nd = ndes(bv);
339         if (hasDown()) {
340                 int des = dy0(bv) + dimdown.descent();
341                 dim.des = max(nd, des);
342         } else
343                 dim.des = nd;
344 }
345
346
347 void InsetMathScript::draw(PainterInfo & pi, int x, int y) const
348 {
349         Changer dummy2 = pi.base.changeEnsureMath();
350         BufferView & bv = *pi.base.bv;
351         if (!nuc().empty())
352                 nuc().draw(pi, x + dxx(bv), y);
353         else {
354                 nuc().setXY(bv, x + dxx(bv), y);
355                 if (editing(&bv))
356                         pi.draw(x + dxx(bv), y, char_type('.'));
357         }
358         Changer dummy = pi.base.changeScript();
359         if (hasUp())
360                 up().draw(pi, x + dx1(bv), y - dy1(bv));
361         if (hasDown())
362                 down().draw(pi, x + dx0(bv), y + dy0(bv));
363 }
364
365
366 void InsetMathScript::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
367 {
368         if (hasUp())
369                 up().metricsT(mi, dim);
370         if (hasDown())
371                 down().metricsT(mi, dim);
372         nuc().metricsT(mi, dim);
373 }
374
375
376 void InsetMathScript::drawT(TextPainter & pain, int x, int y) const
377 {
378         // FIXME: BROKEN
379         if (!nuc().empty())
380                 nuc().drawT(pain, x + 1, y);
381         if (hasUp())
382                 up().drawT(pain, x + 1, y - 1 /*dy1()*/);
383         if (hasDown())
384                 down().drawT(pain, x + 1, y + 1 /*dy0()*/);
385 }
386
387
388 bool InsetMathScript::hasLimits(MathStyle const & style) const
389 {
390         if (nuc().empty())
391                 return false;
392
393         Limits const lim = nuc().back()->limits() == AUTO_LIMITS
394                 ? nuc().back()->defaultLimits(style == DISPLAY_STYLE)
395                 : nuc().back()->limits();
396         LASSERT(lim != AUTO_LIMITS, return false);
397         return lim == LIMITS;
398 }
399
400
401 void InsetMathScript::removeScript(bool up)
402 {
403         if (nargs() == 2) {
404                 if (up == cell_1_is_up_)
405                         cells_.pop_back();
406         } else if (nargs() == 3) {
407                 if (up) {
408                         swap(cells_[1], cells_[2]);
409                         cell_1_is_up_ = false;
410                 } else {
411                         cell_1_is_up_ = true;
412                 }
413                 cells_.pop_back();
414         }
415 }
416
417
418 bool InsetMathScript::has(bool up) const
419 {
420         return idxOfScript(up);
421 }
422
423
424 bool InsetMathScript::hasUp() const
425 {
426         //lyxerr << "1up: " << bool(cell_1_is_up_));
427         //lyxerr << "hasUp: " << bool(idxOfScript(true)));
428         return idxOfScript(true);
429 }
430
431
432 bool InsetMathScript::hasDown() const
433 {
434         //LYXERR0("1up: " << bool(cell_1_is_up_));
435         //LYXERR0("hasDown: " << bool(idxOfScript(false)));
436         return idxOfScript(false);
437 }
438
439
440 idx_type InsetMathScript::idxOfScript(bool up) const
441 {
442         if (nargs() == 1)
443                 return 0;
444         if (nargs() == 2)
445                 return (cell_1_is_up_ == up) ? 1 : 0;
446         if (nargs() == 3)
447                 return up ? 1 : 2;
448         LASSERT(false, return 0);
449 }
450
451
452 bool InsetMathScript::idxForward(Cursor &) const
453 {
454         return false;
455 }
456
457
458 bool InsetMathScript::idxBackward(Cursor &) const
459 {
460         return false;
461 }
462
463
464 bool InsetMathScript::idxUpDown(Cursor & cur, bool up) const
465 {
466         // in nucleus?
467         if (cur.idx() == 0) {
468                 // don't go up/down if there is no cell in this direction
469                 if (!has(up))
470                         return false;
471                 // go up/down only if in the last position
472                 // or in the first position of something with displayed limits
473                 if (cur.pos() == cur.lastpos()
474                                  || (cur.pos() == 0 && has_limits_)) {
475                         cur.idx() = idxOfScript(up);
476                         cur.pos() = 0;
477                         return true;
478                 }
479                 return false;
480         }
481
482         // Are we 'up'?
483         if (cur.idx() == idxOfScript(true)) {
484                 // can't go further up
485                 if (up)
486                         return false;
487                 // otherwise go to last position in the nucleus
488                 cur.idx() = 0;
489                 cur.pos() = cur.lastpos();
490                 return true;
491         }
492
493         // Are we 'down'?
494         if (cur.idx() == idxOfScript(false)) {
495                 // can't go further down
496                 if (!up)
497                         return false;
498                 // otherwise go to last position in the nucleus
499                 cur.idx() = 0;
500                 cur.pos() = cur.lastpos();
501                 return true;
502         }
503
504         return false;
505 }
506
507
508 void InsetMathScript::write(TeXMathStream & os) const
509 {
510         MathEnsurer ensurer(os);
511
512         if (!nuc().empty()) {
513                 os << nuc();
514                 // Avoid double superscript errors (bug 1633)
515                 if (os.latex() && hasUp() && nuc().back()->getChar() == '\'')
516                         os << "{}";
517         } else if (os.firstitem())
518                 LYXERR(Debug::MATHED, "suppressing {} when writing");
519         else
520                 os << "{}";
521
522         if (hasDown() /*&& !down().empty()*/)
523                 os << "_{" << down() << '}';
524
525         if (hasUp() /*&& !up().empty()*/) {
526                 // insert space if up() is empty or an empty brace inset
527                 // (see bug 8305)
528                 if (os.latex() && (up().empty() ||
529                     (up().size() == 1 && up().back()->asBraceInset() &&
530                      up().back()->asBraceInset()->cell(0).empty())))
531                         os << "^ {}";
532                 else {
533                         os << "^{" << up() << '}';
534                         // Avoid double superscript errors by writing an
535                         // empty group {} when a prime immediately follows
536                         if (os.latex())
537                                 os.useBraces(true);
538                 }
539         }
540
541         if (lock_ && !os.latex())
542                 os << "\\lyxlock ";
543 }
544
545
546 void InsetMathScript::normalize(NormalStream & os) const
547 {
548         bool d = hasDown() && !down().empty();
549         bool u = hasUp() && !up().empty();
550
551         if (u && d)
552                 os << "[subsup ";
553         else if (u)
554                 os << "[sup ";
555         else if (d)
556                 os << "[sub ";
557
558         if (!nuc().empty())
559                 os << nuc() << ' ';
560         else
561                 os << "[par]";
562
563         if (u && d)
564                 os << down() << ' ' << up() << ']';
565         else if (d)
566                 os << down() << ']';
567         else if (u)
568                 os << up() << ']';
569 }
570
571
572 void InsetMathScript::maple(MapleStream & os) const
573 {
574         if (!nuc().empty())
575                 os << nuc();
576         if (hasDown() && !down().empty())
577                 os << '[' << down() << ']';
578         if (hasUp() && !up().empty())
579                 os << "^(" << up() << ')';
580 }
581
582
583 void InsetMathScript::mathematica(MathematicaStream & os) const
584 {
585         bool d = hasDown() && !down().empty();
586         bool u = hasUp() && !up().empty();
587
588         if (!nuc().empty()) {
589                 if (d)
590                         os << "Subscript[" << nuc();
591                 else
592                         os << nuc();
593         }
594
595         if (u)
596                 os << "^(" << up() << ')';
597
598         if (!nuc().empty()) {
599                 if (d)
600                         os << ',' << down() << ']';
601         }
602 }
603
604
605 void InsetMathScript::mathmlize(MathMLStream & ms) const
606 {
607         bool d = hasDown() && !down().empty();
608         bool u = hasUp() && !up().empty();
609         bool has_limits = hasLimits(ms.getFontMathStyle());
610
611         if (!d && !u)
612                 return;
613
614         const char * tag = nullptr;
615         if (u && d)
616                 tag = has_limits ? "munderover" : "msubsup";
617         else if (u)
618                 tag = has_limits ? "mover" : "msup";
619         else if (d)
620                 tag = has_limits ? "munder" : "msub";
621
622         ms << MTag(tag);
623
624         if (!nuc().empty())
625                 ms << nuc();
626         else
627                 // TODO: is this empty <mrow> required?
628                 ms << CTag("mrow");
629
630         // No need to wrap these in an <mrow>, as it's done by MathExtern.
631         // More details in https://www.lyx.org/trac/ticket/12221#comment:10.
632         if (d)
633                 ms << down();
634         if (u)
635                 ms << up();
636
637         ms << ETag(tag);
638 }
639
640
641 void InsetMathScript::htmlize(HtmlStream & os) const
642 {
643         bool d = hasDown() && !down().empty();
644         bool u = hasUp() && !up().empty();
645
646         if (!nuc().empty())
647                 os << nuc();
648
649         if (u && d)
650                 os << MTag("span", "class='scripts'")
651                          << MTag("span") << up() << ETag("span")
652                          << MTag("span") << down() << ETag("span")
653                          << ETag("span");
654         else if (u)
655                 os << MTag("sup", "class='math'") << up() << ETag("sup");
656         else if (d)
657                 os << MTag("sub", "class='math'") << down() << ETag("sub");
658 }
659
660
661 void InsetMathScript::octave(OctaveStream & os) const
662 {
663         if (!nuc().empty())
664                 os << nuc();
665         if (hasDown() && !down().empty())
666                 os << '[' << down() << ']';
667         if (hasUp() && !up().empty())
668                 os << "^(" << up() << ')';
669 }
670
671
672 void InsetMathScript::infoize(odocstream & os) const
673 {
674         os << "Scripts";
675 }
676
677
678 void InsetMathScript::infoize2(odocstream & os) const
679 {
680         os << from_ascii(has_limits_ == 1 ? ", Displayed limits" : ", Inlined limits");
681 }
682
683
684 bool InsetMathScript::notifyCursorLeaves(Cursor const & old, Cursor & cur)
685 {
686         InsetMathNest::notifyCursorLeaves(old, cur);
687
688         //LYXERR0("InsetMathScript::notifyCursorLeaves: 1 " << cur);
689
690         // Remove empty scripts if possible:
691
692         // The case of two scripts, but only one got empty (1 = super, 2 = sub).
693         // We keep the script inset, but remove the empty script.
694         if (nargs() > 2 && (!cell(1).empty() || !cell(2).empty())) {
695                 if (cell(2).empty()) {
696                         // must be a subscript...
697                         old.recordUndoInset();
698                         removeScript(false);
699                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
700                         return true;
701                 } else if (cell(1).empty()) {
702                         // must be a superscript...
703                         old.recordUndoInset();
704                         removeScript(true);
705                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
706                         return true;
707                 }
708         }
709         // Now the two suicide cases:
710         // * we have only one script which is empty
711         // * we have two scripts which are both empty.
712         // The script inset is removed completely.
713         if ((nargs() == 2 && cell(1).empty())
714             || (nargs() == 3 && cell(1).empty() && cell(2).empty())) {
715                 // Make undo step. We cannot use cur for this because
716                 // it does not necessarily point to us anymore. But we
717                 // should be on top of the cursor old.
718                 Cursor insetCur = old;
719                 int scriptSlice = insetCur.find(this);
720                 LASSERT(scriptSlice != -1, /**/);
721                 insetCur.cutOff(scriptSlice);
722                 insetCur.recordUndoInset();
723
724                 // Let the script inset commit suicide. This is
725                 // modelled on Cursor.pullArg(), but tries not to
726                 // invoke notifyCursorLeaves again and does not touch
727                 // cur (since the top slice will be deleted
728                 // afterwards)
729                 MathData ar = cell(0);
730                 insetCur.pop();
731                 insetCur.cell().erase(insetCur.pos());
732                 insetCur.cell().insert(insetCur.pos(), ar);
733
734                 // redraw
735                 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
736                 return true;
737         }
738
739         //LYXERR0("InsetMathScript::notifyCursorLeaves: 2 " << cur);
740         return false;
741 }
742
743
744 // the idea for dual scripts came from the eLyXer code
745 void InsetMathScript::validate(LaTeXFeatures & features) const
746 {
747         if (features.runparams().math_flavor == OutputParams::MathAsHTML)
748                 features.addCSSSnippet(
749                         "span.scripts{display: inline-block; vertical-align: middle; text-align:center; font-size: 75%;}\n"
750                         "span.scripts span {display: block;}\n"
751                         "sub.math{font-size: 75%;}\n"
752                         "sup.math{font-size: 75%;}");
753         InsetMathNest::validate(features);
754 }
755
756 } // namespace lyx