1 /**
2  * \file InsetMathSideset.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  * \author Georg Baum
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11 
12 #include <config.h>
13 
14 #include "InsetMathSideset.h"
15 
16 #include "InsetMathSymbol.h"
17 #include "MathData.h"
18 #include "MathStream.h"
19 #include "MathSupport.h"
20 
21 #include "BufferView.h"
22 #include "Cursor.h"
23 #include "DispatchResult.h"
24 #include "FuncRequest.h"
25 #include "FuncStatus.h"
26 #include "LaTeXFeatures.h"
27 #include "MetricsInfo.h"
28 
29 #include "support/debug.h"
30 #include "support/lassert.h"
31 
32 
33 using namespace std;
34 
35 
36 namespace {
37 	/// x spacing between the nucleus and the scripts
38 	int const dx = 2;
39 } // namespace
40 
41 
42 namespace lyx {
43 
InsetMathSideset(Buffer * buf,bool scriptl,bool scriptr)44 InsetMathSideset::InsetMathSideset(Buffer * buf, bool scriptl, bool scriptr)
45 	: InsetMathNest(buf, 3 + scriptl + scriptr), scriptl_(scriptl),
46 	  scriptr_(scriptr)
47 {}
48 
49 
InsetMathSideset(Buffer * buf,bool scriptl,bool scriptr,MathAtom const & at)50 InsetMathSideset::InsetMathSideset(Buffer * buf, bool scriptl, bool scriptr,
51                                    MathAtom const & at)
52 	: InsetMathNest(buf, 3 + scriptl + scriptr), scriptl_(scriptl),
53 	  scriptr_(scriptr)
54 {
55 	nuc().push_back(at);
56 }
57 
58 
clone() const59 Inset * InsetMathSideset::clone() const
60 {
61 	return new InsetMathSideset(*this);
62 }
63 
64 
idxFirst(Cursor & cur) const65 bool InsetMathSideset::idxFirst(Cursor & cur) const
66 {
67 	cur.idx() = 0;
68 	cur.pos() = 0;
69 	return true;
70 }
71 
72 
idxLast(Cursor & cur) const73 bool InsetMathSideset::idxLast(Cursor & cur) const
74 {
75 	cur.idx() = 0;
76 	cur.pos() = nuc().size();
77 	return true;
78 }
79 
80 
dybt(BufferView const & bv,int asc,int des,bool top) const81 int InsetMathSideset::dybt(BufferView const & bv, int asc, int des, bool top) const
82 {
83 	bool isCharBox = nuc().empty() ? false : isAlphaSymbol(nuc().back());
84 	int dasc = 0;
85 	if (scriptl_ && scriptr_)
86 		dasc = max(bl().dimension(bv).ascent(), br().dimension(bv).ascent());
87 	else if (scriptl_)
88 		dasc = bl().dimension(bv).ascent();
89 	else if (scriptr_)
90 		dasc = br().dimension(bv).ascent();
91 	int slevel = nuc().slevel();
92 	int ascdrop = dasc - slevel;
93 	int desdrop = isCharBox ? 0 : des + nuc().sshift();
94 	int mindes = nuc().mindes();
95 	des = max(desdrop, ascdrop);
96 	des = max(mindes, des);
97 	int minasc = nuc().minasc();
98 	ascdrop = 0;
99 	if (!isCharBox && (scriptl_ || scriptr_)) {
100 		if (scriptl_ && scriptr_)
101 			ascdrop = asc - min(tl().mindes(), tr().mindes());
102 		else if (scriptl_)
103 			ascdrop = asc - tl().mindes();
104 		else if (scriptr_)
105 			ascdrop = asc - tr().mindes();
106 	}
107 	int udes = 0;
108 	if (scriptl_)
109 		udes = bl().dimension(bv).descent();
110 	if (scriptr_)
111 		udes = max(udes, br().dimension(bv).descent());
112 	asc = udes + nuc().sshift();
113 	asc = max(ascdrop, asc);
114 	asc = max(minasc, asc);
115 	int del = asc - udes - dasc;
116 	if (del + des <= 2) {
117 		int newdes = 2 - del;
118 		del = slevel - asc + udes;
119 		if (del > 0) {
120 			asc += del;
121 			newdes -= del;
122 		}
123 		des = max(des, newdes);
124 	}
125 	return top ? asc : des;
126 }
127 
128 
dyb(BufferView const & bv) const129 int InsetMathSideset::dyb(BufferView const & bv) const
130 {
131 	int nd = ndes(bv);
132 	int na = nasc(bv);
133 	int des = dybt(bv, na, nd, false);
134 	return des;
135 }
136 
137 
dyt(BufferView const & bv) const138 int InsetMathSideset::dyt(BufferView const & bv) const
139 {
140 	int na = nasc(bv);
141 	int nd = ndes(bv);
142 	int asc = dybt(bv, na, nd, true);
143 	return asc;
144 }
145 
146 
dxr(BufferView const & bv) const147 int InsetMathSideset::dxr(BufferView const & bv) const
148 {
149 	return dxn(bv) + nwid(bv) + dx;
150 }
151 
152 
dxn(BufferView const & bv) const153 int InsetMathSideset::dxn(BufferView const & bv) const
154 {
155 	Dimension const dimb = bl().dimension(bv);
156 	Dimension const dimt = tl().dimension(bv);
157 	return max(dimb.width(), dimt.width()) + dx;
158 }
159 
160 
nwid(BufferView const & bv) const161 int InsetMathSideset::nwid(BufferView const & bv) const
162 {
163 	return nuc().dimension(bv).width();
164 }
165 
166 
nasc(BufferView const & bv) const167 int InsetMathSideset::nasc(BufferView const & bv) const
168 {
169 	return nuc().dimension(bv).ascent();
170 }
171 
172 
ndes(BufferView const & bv) const173 int InsetMathSideset::ndes(BufferView const & bv) const
174 {
175 	return nuc().dimension(bv).descent();
176 }
177 
178 
nker(BufferView const * bv) const179 int InsetMathSideset::nker(BufferView const * bv) const
180 {
181 	int const kerning = nuc().kerning(bv);
182 	return max(kerning, 0);
183 }
184 
185 
metrics(MetricsInfo & mi,Dimension & dim) const186 void InsetMathSideset::metrics(MetricsInfo & mi, Dimension & dim) const
187 {
188 	Changer dummy2 = mi.base.changeEnsureMath();
189 	Dimension dimn;
190 	Dimension dimbl;
191 	Dimension dimtl;
192 	Dimension dimbr;
193 	Dimension dimtr;
194 	nuc().metrics(mi, dimn);
195 	if (!scriptl_) {
196 		bl().metrics(mi, dimbl);
197 		dimtl = dimbl;
198 	}
199 	if (!scriptr_) {
200 		br().metrics(mi, dimbr);
201 		dimtr = dimbr;
202 	}
203 	Changer dummy = mi.base.changeScript();
204 	if (scriptl_) {
205 		bl().metrics(mi, dimbl);
206 		tl().metrics(mi, dimtl);
207 	}
208 	if (scriptr_) {
209 		br().metrics(mi, dimbr);
210 		tr().metrics(mi, dimtr);
211 	}
212 
213 	BufferView & bv = *mi.base.bv;
214 	// FIXME: data copying... not very efficient.
215 
216 	dim.wid = nwid(bv) + nker(mi.base.bv) + 2 * dx;
217 	dim.wid += max(dimbl.width(), dimtl.width());
218 	dim.wid += max(dimbr.width(), dimtr.width());
219 	int na = nasc(bv);
220 	int asc = dyt(bv) + max(dimtl.ascent(), dimtr.ascent());
221 	dim.asc = max(na, asc);
222 	int nd = ndes(bv);
223 	int des = dyb(bv) + max(dimbl.descent(), dimbr.descent());
224 	dim.des = max(nd, des);
225 }
226 
227 
draw(PainterInfo & pi,int x,int y) const228 void InsetMathSideset::draw(PainterInfo & pi, int x, int y) const
229 {
230 	Changer dummy2 = pi.base.changeEnsureMath();
231 	BufferView & bv = *pi.base.bv;
232 	nuc().draw(pi, x + dxn(bv), y);
233 	if (!scriptl_)
234 		bl().draw(pi, x          , y);
235 	if (!scriptr_)
236 		br().draw(pi, x + dxr(bv), y);
237 	Changer dummy = pi.base.changeScript();
238 	if (scriptl_) {
239 		bl().draw(pi, x          , y + dyb(bv));
240 		tl().draw(pi, x          , y - dyt(bv));
241 	}
242 	if (scriptr_) {
243 		br().draw(pi, x + dxr(bv), y + dyb(bv));
244 		tr().draw(pi, x + dxr(bv), y - dyt(bv));
245 	}
246 }
247 
248 
metricsT(TextMetricsInfo const & mi,Dimension & dim) const249 void InsetMathSideset::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
250 {
251 	bl().metricsT(mi, dim);
252 	tl().metricsT(mi, dim);
253 	br().metricsT(mi, dim);
254 	tr().metricsT(mi, dim);
255 	nuc().metricsT(mi, dim);
256 }
257 
258 
drawT(TextPainter & pain,int x,int y) const259 void InsetMathSideset::drawT(TextPainter & pain, int x, int y) const
260 {
261 	// FIXME: BROKEN
262 	nuc().drawT(pain, x + 1, y);
263 	bl().drawT(pain, x + 1, y + 1 /*dy0()*/);
264 	tl().drawT(pain, x + 1, y - 1 /*dy1()*/);
265 	br().drawT(pain, x + 1, y + 1 /*dy0()*/);
266 	tr().drawT(pain, x + 1, y - 1 /*dy1()*/);
267 }
268 
269 
270 
idxForward(Cursor & cur) const271 bool InsetMathSideset::idxForward(Cursor & cur) const
272 {
273 	if (!scriptl_ && cur.idx() == 1) {
274 		// left => nucleus
275 		cur.idx() = 0;
276 		return true;
277 	} else if (!scriptr_ && cur.idx() == 0) {
278 		// nucleus => right
279 		cur.idx() = 2 + scriptl_;
280 		return true;
281 	}
282 	return false;
283 }
284 
285 
idxBackward(Cursor & cur) const286 bool InsetMathSideset::idxBackward(Cursor & cur) const
287 {
288 	if (!scriptr_ && cur.idx() == (scriptl_ ? 3 : 2)) {
289 		// right => nucleus
290 		cur.idx() = 0;
291 		return true;
292 	} else if (!scriptl_ && cur.idx() == 0) {
293 		// nucleus => left
294 		cur.idx() = 1;
295 		return true;
296 	}
297 	return false;
298 }
299 
300 
idxUpDown(Cursor & cur,bool up) const301 bool InsetMathSideset::idxUpDown(Cursor & cur, bool up) const
302 {
303 	// in nucleus?
304 	if (cur.idx() == 0) {
305 		// go up/down only if in the last position
306 		// or in the first position
307 		if ((scriptr_ && cur.pos() == cur.lastpos()) ||
308 		    (scriptl_ && cur.pos() == 0)) {
309 			if (cur.pos() == 0)
310 				cur.idx() = up ? 2 : 1;
311 			else
312 				cur.idx() = (up ? 3 : 2) + scriptl_;
313 			cur.pos() = 0;
314 			return true;
315 		}
316 		return false;
317 	}
318 
319 	// Are we 'up'?
320 	if ((scriptl_ && cur.idx() == 2) ||
321 	    (scriptr_ && cur.idx() == (scriptl_ ? 4 : 3))) {
322 		// can't go further up
323 		if (up)
324 			return false;
325 		// otherwise go to first or last position in the nucleus
326 		cur.idx() = 0;
327 		if (cur.idx() == 2)
328 			cur.pos() = 0;
329 		else
330 			cur.pos() = cur.lastpos();
331 		return true;
332 	}
333 
334 	// Are we 'down'?
335 	if ((scriptl_ && cur.idx() == 1) ||
336 	    (scriptr_ && cur.idx() == (scriptl_ ? 3 : 2))) {
337 		// can't go further down
338 		if (!up)
339 			return false;
340 		// otherwise go to first or last position in the nucleus
341 		cur.idx() = 0;
342 		if (cur.idx() == 1)
343 			cur.pos() = 0;
344 		else
345 			cur.pos() = cur.lastpos();
346 		return true;
347 	}
348 
349 	return false;
350 }
351 
352 
write(WriteStream & os) const353 void InsetMathSideset::write(WriteStream & os) const
354 {
355 	MathEnsurer ensurer(os);
356 
357 	os << "\\sideset";
358 	os << '{';
359 	if (scriptl_) {
360 		if (!bl().empty())
361 			os << "_{" << bl() << '}';
362 		if (!tl().empty())
363 			os << "^{" << tl() << '}';
364 	} else
365 		os << bl();
366 	os << "}{";
367 	if (scriptr_) {
368 		if (!br().empty())
369 			os << "_{" << br() << '}';
370 		if (!tr().empty())
371 			os << "^{" << tr() << '}';
372 	} else
373 		os << br();
374 	os << '}';
375 	os << nuc();
376 
377 	if (lock_ && !os.latex())
378 		os << "\\lyxlock ";
379 }
380 
381 
normalize(NormalStream & os) const382 void InsetMathSideset::normalize(NormalStream & os) const
383 {
384 	os << "[sideset ";
385 
386 	if (!bl().empty())
387 		os << bl() << ' ';
388 	if (scriptl_ && !tl().empty())
389 		os << tl() << ' ';
390 
391 	if (!nuc().empty())
392 		os << nuc() << ' ';
393 	else
394 		os << "[par]";
395 
396 	if (!br().empty())
397 		os << br() << ' ';
398 	if (scriptr_ && !tr().empty())
399 		os << tr() << ' ';
400 	os << ']';
401 }
402 
403 
mathmlize(MathStream & os) const404 void InsetMathSideset::mathmlize(MathStream & os) const
405 {
406 	// FIXME This is only accurate if both scriptl_ and scriptr_ are true
407 	if (!scriptl_)
408 		os << MTag("mrow") << bl() << ETag("mrow");
409 	if (scriptl_ || scriptr_) {
410 		os << MTag("mmultiscripts");
411 
412 		if (nuc().empty())
413 			os << "<mrow />";
414 		else
415 			os << MTag("mrow") << nuc() << ETag("mrow");
416 
417 		if (br().empty() || !scriptr_)
418 			os << "<none />";
419 		else
420 			os << MTag("mrow") << br() << ETag("mrow");
421 		if (tr().empty() || !scriptr_)
422 			os << "<none />";
423 		else
424 			os << MTag("mrow") << tr() << ETag("mrow");
425 
426 		if (bl().empty() || !scriptl_)
427 			os << "<none />";
428 		else
429 			os << MTag("mrow") << bl() << ETag("mrow");
430 		if (tl().empty() || !scriptl_)
431 			os << "<none />";
432 		else
433 			os << MTag("mrow") << tl() << ETag("mrow");
434 
435 		os << ETag("mmultiscripts");
436 	}
437 	if (!scriptr_)
438 		os << MTag("mrow") << br() << ETag("mrow");
439 }
440 
441 
htmlize(HtmlStream & os) const442 void InsetMathSideset::htmlize(HtmlStream & os) const
443 {
444 	// FIXME This is only accurate if both scriptl_ and scriptr_ are true
445 	bool const havebl = scriptl_ && !bl().empty();
446 	bool const havetl = scriptl_ && !tl().empty();
447 	bool const havebr = scriptr_ && !br().empty();
448 	bool const havetr = scriptr_ && !tr().empty();
449 
450 	if (!scriptl_ && !bl().empty())
451 		os << bl();
452 
453 	if (havebl && havetl)
454 		os << MTag("span", "class='scripts'")
455 			 << MTag("span") << tl() << ETag("span")
456 			 << MTag("span") << bl() << ETag("span")
457 			 << ETag("span");
458 	else if (havebl)
459 		os << MTag("sub", "class='math'") << bl() << ETag("sub");
460 	else if (havetl)
461 		os << MTag("sup", "class='math'") << tl() << ETag("sup");
462 
463 	if (!nuc().empty())
464 		os << nuc();
465 
466 	if (havebr && havetr)
467 		os << MTag("span", "class='scripts'")
468 			 << MTag("span") << tr() << ETag("span")
469 			 << MTag("span") << br() << ETag("span")
470 			 << ETag("span");
471 	else if (havebr)
472 		os << MTag("sub", "class='math'") << br() << ETag("sub");
473 	else if (havetr)
474 		os << MTag("sup", "class='math'") << tr() << ETag("sup");
475 
476 	if (!scriptr_ && !br().empty())
477 		os << br();
478 }
479 
480 
infoize(odocstream & os) const481 void InsetMathSideset::infoize(odocstream & os) const
482 {
483 	os << "Sideset";
484 }
485 
486 
487 // the idea for dual scripts came from the eLyXer code
validate(LaTeXFeatures & features) const488 void InsetMathSideset::validate(LaTeXFeatures & features) const
489 {
490 	if (features.runparams().math_flavor == OutputParams::MathAsHTML)
491 		features.addCSSSnippet(
492 			"span.scripts{display: inline-block; vertical-align: middle; text-align:center; font-size: 75%;}\n"
493 			"span.scripts span {display: block;}\n"
494 			"sub.math{font-size: 75%;}\n"
495 			"sup.math{font-size: 75%;}");
496 	features.require("amsmath");
497 	InsetMathNest::validate(features);
498 }
499 
500 } // namespace lyx
501