]> git.lyx.org Git - lyx.git/blob - src/mathed/math_extern.C
fonts as insets
[lyx.git] / src / mathed / math_extern.C
1 // This file contains most of the magic that extracts "context
2 // information" from the unstructered layout-oriented stuff in an
3 // MathArray.
4
5 #include <algorithm>
6
7 #include "math_amsarrayinset.h"
8 #include "math_charinset.h"
9 #include "math_deliminset.h"
10 #include "math_diffinset.h"
11 #include "math_exfuncinset.h"
12 #include "math_exintinset.h"
13 #include "math_fracinset.h"
14 #include "math_matrixinset.h"
15 #include "math_mathmlstream.h"
16 #include "math_scriptinset.h"
17 #include "math_stringinset.h"
18 #include "math_symbolinset.h"
19 #include "math_unknowninset.h"
20 #include "Lsstream.h"
21 #include "debug.h"
22
23
24 using std::ostream;
25 using std::istringstream;
26 using std::find_if;
27
28
29 ostream & operator<<(ostream & os, MathArray const & ar)
30 {
31         NormalStream ns(os);
32         ns << ar;
33         return os;
34 }
35
36
37 // define a function for tests
38 typedef bool TestItemFunc(MathInset *);
39
40 // define a function for replacing subexpressions
41 typedef MathInset * ReplaceArgumentFunc(const MathArray & ar);
42
43
44
45 // try to extract a super/subscript
46 // modify iterator position to point behind the thing
47 bool extractScript(MathArray & ar,
48         MathArray::iterator & pos, MathArray::iterator last)
49 {
50         // nothing to get here
51         if (pos == last)
52                 return false;
53
54         // is this a scriptinset?
55         if (!(*pos)->asScriptInset())
56                 return false;
57
58         // it is a scriptinset, use it.
59         ar.push_back(*pos);
60         ++pos;
61         return true;
62 }
63
64
65 // try to extract an "argument" to some function.
66 // returns position behind the argument
67 MathArray::iterator extractArgument(MathArray & ar,
68         MathArray::iterator pos, MathArray::iterator last, string const & = "")
69 {
70         // nothing to get here
71         if (pos == last)
72                 return pos;
73
74         // something deliminited _is_ an argument
75         if ((*pos)->asDelimInset()) {
76                 ar.push_back(*pos);
77                 return pos + 1;
78         }
79
80         // always take the first thing, no matter what it is
81         ar.push_back(*pos);
82
83         // go ahead if possible
84         ++pos;
85         if (pos == last)
86                 return pos;
87
88         // if the next item is a subscript, it most certainly belongs to the
89         // thing we have
90         extractScript(ar, pos, last);
91         if (pos == last)
92                 return pos;
93
94         // but it might be more than that.
95         // FIXME: not implemented
96         //for (MathArray::iterator it = pos + 1; it != last; ++it) {
97         //      // always take the first thing, no matter
98         //      if (it == pos) {
99         //              ar.push_back(*it);
100         //              continue;
101         //      }
102         //}
103         return pos;
104 }
105
106
107 MathScriptInset const * asScript(MathArray::const_iterator it)
108 {
109         if (!it->nucleus())
110                 return 0;
111         if (it->nucleus()->asScriptInset())
112                 return 0;
113         ++it;
114         if (!it->nucleus())
115                 return 0;
116         return it->nucleus()->asScriptInset();
117 }
118
119
120
121 // returns sequence of char with same code starting at it up to end
122 // it might be less, though...
123 MathArray::const_iterator charSequence(MathArray::const_iterator it,
124         MathArray::const_iterator end, string & s)
125 {
126         for (; it != end && (*it)->asCharInset(); ++it)
127                 s += (*it)->getChar();
128         return it;
129 }
130
131
132 void extractStrings(MathArray & ar)
133 {
134         //lyxerr << "\nStrings from: " << ar << "\n";
135         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
136                 MathArray::iterator it = ar.begin() + i;
137                 if (!(*it)->asCharInset())
138                         continue;
139
140                 // create proper string inset
141                 MathStringInset * p = new MathStringInset;
142                 MathArray::const_iterator
143                         jt = charSequence(it, ar.end(), p->str_);
144
145                 // clean up
146                 (*it).reset(p);
147                 ar.erase(i + 1, jt - ar.begin());
148         }
149         //lyxerr << "\nStrings to: " << ar << "\n";
150 }
151
152
153 MathInset * singleItem(MathArray & ar)
154 {
155         return ar.size() == 1 ? ar.begin()->nucleus() : 0;
156 }
157
158
159 void extractMatrices(MathArray & ar)
160 {
161         //lyxerr << "\nMatrices from: " << ar << "\n";
162         // first pass for explicitly delimited stuff
163         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
164                 MathDelimInset * del = (*it)->asDelimInset();
165                 if (!del)
166                         continue;
167                 MathInset * arr = singleItem(del->cell(0));
168                 if (!arr || !arr->asGridInset())
169                         continue;
170                 *it = MathAtom(new MathMatrixInset(*(arr->asGridInset())));
171         }
172
173         // second pass for AMS "pmatrix" etc
174         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
175                 MathAMSArrayInset * ams = (*it)->asAMSArrayInset();
176                 if (!ams)
177                         continue;
178                 *it = MathAtom(new MathMatrixInset(*ams));
179         }
180         //lyxerr << "\nMatrices to: " << ar << "\n";
181 }
182
183
184 // convert this inset somehow to a string
185 bool extractString(MathInset * p, string & str)
186 {
187         if (!p)
188                 return false;
189         if (p->getChar()) {
190                 str = string(1, p->getChar());
191                 return true;
192         }
193         if (p->asStringInset()) {
194                 str = p->asStringInset()->str();
195                 return true;
196         }
197         return false;
198 }
199
200
201 // convert this inset somehow to a number
202 bool extractNumber(MathArray const & ar, int & i)
203 {
204         string s;
205         charSequence(ar.begin(), ar.end(), s);
206         istringstream is(s.c_str());
207         is >> i;
208         return is;
209 }
210
211
212 bool extractNumber(MathArray const & ar, double & i)
213 {
214         string s;
215         charSequence(ar.begin(), ar.end(), s);
216         istringstream is(s.c_str());
217         is >> i;
218         return is;
219 }
220
221
222 bool testString(MathInset * p, const string & str)
223 {
224         string s;
225         return extractString(p, s) && str == s;
226 }
227
228
229 // search end of nested sequence
230 MathArray::iterator endNestSearch(
231         MathArray::iterator it,
232         MathArray::iterator last,
233         TestItemFunc testOpen,
234         TestItemFunc testClose
235 )
236 {
237         for (int level = 0; it != last; ++it) {
238                 if (testOpen(it->nucleus()))
239                         ++level;
240                 if (testClose(it->nucleus()))
241                         --level;
242                 if (level == 0)
243                         break;
244         }
245         return it;
246 }
247
248
249 // replace nested sequences by a real Insets
250 void replaceNested(
251         MathArray & ar,
252         TestItemFunc testOpen,
253         TestItemFunc testClose,
254         ReplaceArgumentFunc replaceArg
255 )
256 {
257         // use indices rather than iterators for the loop  because we are going
258         // to modify the array.
259         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
260                 // check whether this is the begin of the sequence
261                 MathArray::iterator it = ar.begin() + i;
262                 if (!testOpen(it->nucleus()))
263                         continue;
264
265                 // search end of sequence
266                 MathArray::iterator jt = endNestSearch(it, ar.end(), testOpen, testClose);
267                 if (jt == ar.end())
268                         continue;
269
270                 // create a proper inset as replacement
271                 MathInset * p = replaceArg(MathArray(it + 1, jt));
272
273                 // replace the original stuff by the new inset
274                 ar.erase(it + 1, jt + 1);
275                 (*it).reset(p);
276         }
277 }
278
279
280
281 //
282 // split scripts into seperate super- and subscript insets. sub goes in
283 // front of super...
284 //
285
286 void splitScripts(MathArray & ar)
287 {
288         //lyxerr << "\nScripts from: " << ar << "\n";
289         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
290                 MathArray::iterator it = ar.begin() + i;
291
292                 // is this script inset?
293                 MathScriptInset * p = (*it)->asScriptInset();
294                 if (!p)
295                         continue;
296
297                 // no problem if we don't have both...
298                 if (!p->hasUp() || !p->hasDown())
299                         continue;
300
301                 // create extra script inset and move superscript over
302                 MathScriptInset * q = new MathScriptInset;
303                 q->ensure(true);
304                 q->up().data_.swap(p->up().data_);
305                 p->removeScript(true);
306
307                 // insert new inset behind
308                 ++i;
309                 ar.insert(i, MathAtom(q));
310         }
311         //lyxerr << "\nScripts to: " << ar << "\n";
312 }
313
314
315 //
316 // extract exp(...)
317 //
318
319 void extractExps(MathArray & ar)
320 {
321         //lyxerr << "\nExps from: " << ar << "\n";
322
323         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
324                 MathArray::iterator it = ar.begin() + i;
325
326                 // is this 'e'?
327                 MathCharInset const * p = (*it)->asCharInset();
328                 if (!p || p->getChar() != 'e')
329                         continue;
330
331                 // we need an exponent but no subscript
332                 MathScriptInset * sup = (*(it + 1))->asScriptInset();
333                 if (!sup || sup->hasDown())
334                         continue;
335
336                 // create a proper exp-inset as replacement
337                 MathExFuncInset * func = new MathExFuncInset("exp");
338                 func->cell(0) = sup->cell(1);
339
340                 // clean up
341                 (*it).reset(func);
342                 ar.erase(it + 1);
343         }
344         //lyxerr << "\nExps to: " << ar << "\n";
345 }
346
347
348 //
349 // search deliminiters
350 //
351
352 bool testOpenParan(MathInset * p)
353 {
354         return testString(p, "(");
355 }
356
357
358 bool testCloseParan(MathInset * p)
359 {
360         return testString(p, ")");
361 }
362
363
364 MathInset * replaceDelims(const MathArray & ar)
365 {
366         MathDelimInset * del = new MathDelimInset("(", ")");
367         del->cell(0) = ar;
368         return del;
369 }
370
371
372 // replace '('...')' sequences by a real MathDelimInset
373 void extractDelims(MathArray & ar)
374 {
375         //lyxerr << "\nDelims from: " << ar << "\n";
376         replaceNested(ar, testOpenParan, testCloseParan, replaceDelims);
377         //lyxerr << "\nDelims to: " << ar << "\n";
378 }
379
380
381
382 //
383 // search well-known functions
384 //
385
386
387 // replace 'f' '(...)' and 'f' '^n' '(...)' sequences by a real MathExFuncInset
388 // assume 'extractDelims' ran before
389 void extractFunctions(MathArray & ar)
390 {
391         // we need at least two items...
392         if (ar.size() <= 1)
393                 return;
394
395         //lyxerr << "\nFunctions from: " << ar << "\n";
396         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
397                 MathArray::iterator it = ar.begin() + i;
398                 MathArray::iterator jt = it + 1;
399
400                 string name;
401                 // is it a function?
402                 if ((*it)->asUnknownInset()) {
403                         // it certainly is if it is well known...
404                         name = (*it)->asUnknownInset()->name();
405                 } else {
406                         // is this a user defined function?
407                         // it it probably not, if it doesn't have a name.
408                         if (!extractString((*it).nucleus(), name))
409                                 continue;
410                         // it is not if it has no argument
411                         if (jt == ar.end())
412                                 continue;
413                         // guess so, if this is followed by
414                         // a DelimInset with a single item in the cell
415                         MathDelimInset * del = (*jt)->asDelimInset();
416                         if (!del || del->cell(0).size() != 1)
417                                 continue;
418                         // fall trough into main branch
419                 }
420
421                 // do we have an exponent like in
422                 // 'sin' '^2' 'x' -> 'sin(x)' '^2'
423                 MathArray exp;
424                 extractScript(exp, jt, ar.end());
425
426                 // create a proper inset as replacement
427                 MathExFuncInset * p = new MathExFuncInset(name);
428
429                 // jt points to the "argument". Get hold of this.
430                 MathArray::iterator st = extractArgument(p->cell(0), jt, ar.end());
431
432                 // replace the function name by a real function inset
433                 (*it).reset(p);
434
435                 // remove the source of the argument from the array
436                 ar.erase(it + 1, st);
437
438                 // re-insert exponent
439                 ar.insert(i + 1, exp);
440                 //lyxerr << "\nFunctions to: " << ar << "\n";
441         }
442 }
443
444
445 //
446 // search integrals
447 //
448
449 bool testSymbol(MathInset * p, string const & name)
450 {
451         return p->asSymbolInset() && p->asSymbolInset()->name() == name;
452 }
453
454
455 bool testIntSymbol(MathInset * p)
456 {
457         return testSymbol(p, "int");
458 }
459
460
461 bool testIntDiff(MathInset * p)
462 {
463         return testString(p, "d");
464 }
465
466
467 // replace '\int' ['_^'] x 'd''x'(...)' sequences by a real MathExIntInset
468 // assume 'extractDelims' ran before
469 void extractIntegrals(MathArray & ar)
470 {
471         // we need at least three items...
472         if (ar.size() <= 2)
473                 return;
474
475         //lyxerr << "\nIntegrals from: " << ar << "\n";
476         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
477                 MathArray::iterator it = ar.begin() + i;
478
479                 // is this a integral name?
480                 if (!testIntSymbol(it->nucleus()))
481                         continue;
482
483                 // search 'd'
484                 MathArray::iterator jt =
485                         endNestSearch(it, ar.end(), testIntSymbol, testIntDiff);
486
487                 // something sensible found?
488                 if (jt == ar.end())
489                         continue;
490
491                 // create a proper inset as replacement
492                 MathExIntInset * p = new MathExIntInset("int");
493
494                 // collect subscript if any
495                 MathArray::iterator st = it + 1;
496                 if (st != ar.end())
497                         if (MathScriptInset * sub = (*st)->asScriptInset())
498                                 if (sub->hasDown()) {
499                                         p->cell(2) = sub->down().data_;
500                                         ++st;
501                                 }
502
503                 // collect superscript if any
504                 if (st != ar.end())
505                         if (MathScriptInset * sup = (*st)->asScriptInset())
506                                 if (sup->hasUp()) {
507                                         p->cell(3) = sup->up().data_;
508                                         ++st;
509                                 }
510
511                 // core ist part from behind the scripts to the 'd'
512                 p->cell(0) = MathArray(st, jt);
513
514                 // use the "thing" behind the 'd' as differential
515                 MathArray::iterator tt = extractArgument(p->cell(1), jt + 1, ar.end());
516
517                 // remove used parts
518                 ar.erase(it + 1, tt);
519                 (*it).reset(p);
520         }
521         //lyxerr << "\nIntegrals to: " << ar << "\n";
522 }
523
524
525 //
526 // search sums
527 //
528
529 bool testSumSymbol(MathInset * p)
530 {
531         return testSymbol(p, "sum");
532 }
533
534
535 bool testEqualSign(MathAtom const & at)
536 {
537         return testString(at.nucleus(), "=");
538 }
539
540
541
542 // replace '\sum' ['_^'] f(x) sequences by a real MathExIntInset
543 // assume 'extractDelims' ran before
544 void extractSums(MathArray & ar)
545 {
546         // we need at least two items...
547         if (ar.size() <= 1)
548                 return;
549
550         //lyxerr << "\nSums from: " << ar << "\n";
551         for (MathArray::size_type i = 0; i + 1< ar.size(); ++i) {
552                 MathArray::iterator it = ar.begin() + i;
553
554                 // is this a sum name?
555                 if (!testSumSymbol(it->nucleus()))
556                         continue;
557
558                 // create a proper inset as replacement
559                 MathExIntInset * p = new MathExIntInset("sum");
560
561                 // collect lower bound and summation index
562                 MathArray::iterator st = it + 1;
563                 if (st != ar.end())
564                         if (MathScriptInset * sub = (*st)->asScriptInset())
565                                 if (sub->hasDown()) {
566                                         // try to figure out the summation index from the subscript
567                                         MathArray & ar = sub->down().data_;
568                                         MathArray::iterator it =
569                                                 find_if(ar.begin(), ar.end(), &testEqualSign);
570                                         if (it != ar.end()) {
571                                                 // we found a '=', use everything in front of that as index,
572                                                 // and everything behind as lower index
573                                                 p->cell(1) = MathArray(ar.begin(), it);
574                                                 p->cell(2) = MathArray(it + 1, ar.end());
575                                         } else {
576                                                 // use everything as summation index, don't use scripts.
577                                                 p->cell(1) = ar;
578                                         }
579                                         ++st;
580                                 }
581
582                 // collect upper bound
583                 if (st != ar.end())
584                         if (MathScriptInset * sup = (*st)->asScriptInset())
585                                 if (sup->hasUp()) {
586                                         p->cell(3) = sup->up().data_;
587                                         ++st;
588                                 }
589
590                 // use some  behind the script as core
591                 MathArray::iterator tt = extractArgument(p->cell(0), st, ar.end());
592
593                 // cleanup
594                 ar.erase(it + 1, tt);
595                 (*it).reset(p);
596         }
597         //lyxerr << "\nSums to: " << ar << "\n";
598 }
599
600
601 //
602 // search differential stuff
603 //
604
605 // tests for 'd' or '\partial'
606 bool testDiffItem(MathAtom const & at)
607 {
608         return testString(at.nucleus(), "d");
609 }
610
611
612 bool testDiffArray(MathArray const & ar)
613 {
614         return ar.size() && testDiffItem(ar.front());
615 }
616
617
618 bool testDiffFrac(MathInset * p)
619 {
620         MathFracInset * f = p->asFracInset();
621         return f && testDiffArray(f->cell(0)) && testDiffArray(f->cell(1));
622 }
623
624
625 // is this something like ^number?
626 bool extractDiffExponent(MathArray::iterator it, int & i)
627 {
628         if (!(*it)->asScriptInset())
629                 return false;
630
631         string s;
632         if (!extractString((*it).nucleus(), s))
633                 return false;
634         istringstream is(s.c_str());
635         is >> i;
636         return is;
637 }
638
639
640 void extractDiff(MathArray & ar)
641 {
642         //lyxerr << "\nDiffs from: " << ar << "\n";
643         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
644                 MathArray::iterator it = ar.begin() + i;
645
646                 // is this a "differential fraction"?
647                 if (!testDiffFrac(it->nucleus()))
648                         continue;
649
650                 MathFracInset * f = (*it)->asFracInset();
651                 if (!f) {
652                         lyxerr << "should not happen\n";
653                         continue;
654                 }
655
656                 // create a proper diff inset
657                 MathDiffInset * diff = new MathDiffInset;
658
659                 // collect function, let jt point behind last used item
660                 MathArray::iterator jt = it + 1;
661                 //int n = 1;
662                 MathArray & numer = f->cell(0);
663                 if (numer.size() > 1 && numer.at(1)->asScriptInset()) {
664                         // this is something like  d^n f(x) / d... or  d^n / d...
665                         // FIXME
666                         //n = 1;
667                         if (numer.size() > 2)
668                                 diff->cell(0) = MathArray(numer.begin() + 2, numer.end());
669                         else
670                                 jt = extractArgument(diff->cell(0), jt, ar.end());
671                 } else {
672                         // simply d f(x) / d... or  d/d...
673                         if (numer.size() > 1)
674                                 diff->cell(0) = MathArray(numer.begin() + 1, numer.end());
675                         else
676                                 jt = extractArgument(diff->cell(0), jt, ar.end());
677                 }
678
679                 // collect denominator parts
680                 MathArray & denom = f->cell(1);
681                 for (MathArray::iterator dt = denom.begin(); dt != denom.end();) {
682                         // find the next 'd'
683                         MathArray::iterator et = find_if(dt + 1, denom.end(), &testDiffItem);
684
685                         // point before this
686                         MathArray::iterator st = et - 1;
687                         MathScriptInset * script = (*st)->asScriptInset();
688                         if (script && script->hasUp()) {
689                                 // things like   d.../dx^n
690                                 int mult = 1;
691                                 if (extractNumber(script->up().data_, mult)) {
692                                         //lyxerr << "mult: " << mult << endl;
693                                         for (int i = 0; i < mult; ++i)
694                                                 diff->addDer(MathArray(dt + 1, st));
695                                 }
696                         } else {
697                                 // just  d.../dx
698                                 diff->addDer(MathArray(dt + 1, et));
699                         }
700                         dt = et;
701                 }
702
703                 // cleanup
704                 ar.erase(it + 1, jt);
705                 (*it).reset(diff);
706         }
707         //lyxerr << "\nDiffs to: " << ar << "\n";
708 }
709
710
711
712 //
713 // combine searches
714 //
715
716 void extractStructure(MathArray & ar)
717 {
718         splitScripts(ar);
719         extractMatrices(ar);
720         extractDelims(ar);
721         extractFunctions(ar);
722         extractIntegrals(ar);
723         extractSums(ar);
724         extractDiff(ar);
725         extractExps(ar);
726         extractStrings(ar);
727 }
728
729
730 void write(MathArray const & dat, WriteStream & wi)
731 {
732         MathArray ar = dat;
733         extractStrings(ar);
734         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
735                 wi.firstitem() = (it == ar.begin());
736                 MathInset const * p = it->nucleus();
737                 if (it + 1 != ar.end()) {
738                         if (MathScriptInset const * q = asScript(it)) {
739                                 q->write2(p, wi);
740                                 ++it;
741                                 continue;
742                         }
743                 }
744                 p->write(wi);
745         }
746 }
747
748
749 void normalize(MathArray const & ar, NormalStream & os)
750 {
751         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
752                 (*it)->normalize(os);
753 }
754
755
756 void octavize(MathArray const & dat, OctaveStream & os)
757 {
758         MathArray ar = dat;
759         extractStructure(ar);
760         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
761                 MathInset const * p = it->nucleus();
762                 if (it + 1 != ar.end()) {
763                         if (MathScriptInset const * q = asScript(it)) {
764                                 q->octavize2(p, os);
765                                 ++it;
766                                 continue;
767                         }
768                 }
769                 p->octavize(os);
770         }
771 }
772
773
774 void maplize(MathArray const & dat, MapleStream & os)
775 {
776         MathArray ar = dat;
777         extractStructure(ar);
778         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
779                 MathInset const * p = it->nucleus();
780                 if (it + 1 != ar.end()) {
781                         if (MathScriptInset const * q = asScript(it)) {
782                                 q->maplize2(p, os);
783                                 ++it;
784                                 continue;
785                         }
786                 }
787                 p->maplize(os);
788         }
789 }
790
791
792 void mathmlize(MathArray const & dat, MathMLStream & os)
793 {
794         MathArray ar = dat;
795         extractStructure(ar);
796         if (ar.size() == 0)
797                 os << "<mrow/>";
798         else if (ar.size() == 1)
799                 os << ar.begin()->nucleus();
800         else {
801                 os << MTag("mrow");
802                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
803                         MathInset const * p = it->nucleus();
804                         if (it + 1 != ar.end()) {
805                                 if (MathScriptInset const * q = asScript(it)) {
806                                         q->mathmlize2(p, os);
807                                         ++it;
808                                         continue;
809                                 }
810                         }
811                         p->mathmlize(os);
812                 }
813                 os << ETag("mrow");
814         }
815 }