1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.jeuclid.elements.presentation.token;
20
21 import java.awt.Color;
22 import java.awt.Graphics2D;
23 import java.awt.font.TextLayout;
24 import java.awt.geom.AffineTransform;
25 import java.text.AttributedString;
26
27 import net.sourceforge.jeuclid.Constants;
28 import net.sourceforge.jeuclid.LayoutContext;
29 import net.sourceforge.jeuclid.context.Display;
30 import net.sourceforge.jeuclid.context.Parameter;
31 import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
32 import net.sourceforge.jeuclid.elements.JEuclidElement;
33 import net.sourceforge.jeuclid.elements.presentation.general.Mrow;
34 import net.sourceforge.jeuclid.elements.support.GraphicsSupport;
35 import net.sourceforge.jeuclid.elements.support.attributes.AttributesHelper;
36 import net.sourceforge.jeuclid.elements.support.operatordict.OperatorDictionary;
37 import net.sourceforge.jeuclid.elements.support.operatordict.UnknownAttributeException;
38 import net.sourceforge.jeuclid.elements.support.text.StringUtil;
39 import net.sourceforge.jeuclid.elements.support.text.StringUtil.TextLayoutInfo;
40 import net.sourceforge.jeuclid.layout.LayoutInfo;
41 import net.sourceforge.jeuclid.layout.LayoutStage;
42 import net.sourceforge.jeuclid.layout.LayoutView;
43 import net.sourceforge.jeuclid.layout.TextObject;
44
45 import org.apache.batik.dom.events.DOMCustomEvent;
46 import org.w3c.dom.Node;
47 import org.w3c.dom.events.CustomEvent;
48 import org.w3c.dom.events.Event;
49 import org.w3c.dom.events.EventListener;
50 import org.w3c.dom.events.EventTarget;
51 import org.w3c.dom.mathml.MathMLOperatorElement;
52 import org.w3c.dom.mathml.MathMLUnderOverElement;
53
54
55
56
57
58
59
60
61
62
63 public final class Mo extends AbstractJEuclidElement implements
64 MathMLOperatorElement, EventListener {
65
66
67
68 public static final String ATTR_FORM = "form";
69
70
71 public static final String ATTR_SEPARATOR = "separator";
72
73
74 public static final String ATTR_LSPACE = "lspace";
75
76
77 public static final String ATTR_RSPACE = "rspace";
78
79
80 public static final String ATTR_MINSIZE = "minsize";
81
82
83 public static final String ATTR_MAXSIZE = "maxsize";
84
85
86 public static final String ATTR_MOVEABLEWRONG = "moveablelimits";
87
88
89 public static final String ATTR_MOVABLELIMITS = "movablelimits";
90
91
92 public static final String ATTR_ACCENT = "accent";
93
94
95
96
97 public static final String ELEMENT = "mo";
98
99
100
101
102 public static final float LARGEOP_CORRECTOR_INLINE = (float) 1.2;
103
104
105
106
107 public static final float LARGEOP_CORRECTOR_BLOCK = (float) 1.5;
108
109
110
111
112 public static final String ATTR_STRETCHY = "stretchy";
113
114
115
116
117 public static final String ATTR_LARGEOP = "largeop";
118
119
120
121
122 public static final String ATTR_SYMMETRIC = "symmetric";
123
124
125
126
127 public static final String ATTR_FENCE = "fence";
128
129
130
131
132 public static final String MOEVENT = "MOEvent";
133
134
135
136
137
138
139 public static final String HOR_DELIMITERS =
140 +
141 + "\u033F" + "\u2190" + "\u2192" + "\u2194"
142 +
143 +
144 +
145 +
146
147
148
149
150
151
152 public static final String VER_DELIMITERS = "[{()}]|"
153 +
154 * Up Arrow
155 * Down
156 * Arrow
157 */"\u21C5"
158 +
159 +
160 +
161
162 private static final long serialVersionUID = 1L;
163
164 private final OperatorDictionary opDict;
165
166 private boolean inChangeHook;
167
168
169
170
171
172
173
174
175
176
177 public Mo() {
178 super();
179 this.setDefaultMathAttribute(Mo.ATTR_FORM,
180 OperatorDictionary.FORM_INFIX);
181 this.setDefaultMathAttribute(Mo.ATTR_FENCE, Constants.FALSE);
182 this.setDefaultMathAttribute(Mo.ATTR_SEPARATOR, Constants.FALSE);
183 this.setDefaultMathAttribute(Mo.ATTR_LSPACE,
184 AttributesHelper.THICKMATHSPACE);
185 this.setDefaultMathAttribute(Mo.ATTR_RSPACE,
186 AttributesHelper.THICKMATHSPACE);
187 this.setDefaultMathAttribute(Mo.ATTR_STRETCHY, Constants.FALSE);
188 this.setDefaultMathAttribute(Mo.ATTR_SYMMETRIC, Constants.TRUE);
189 this.setDefaultMathAttribute(Mo.ATTR_MAXSIZE,
190 AttributesHelper.INFINITY);
191 this.setDefaultMathAttribute(Mo.ATTR_MINSIZE, "1");
192 this.setDefaultMathAttribute(Mo.ATTR_LARGEOP, Constants.FALSE);
193 this.setDefaultMathAttribute(Mo.ATTR_MOVABLELIMITS, Constants.FALSE);
194 this.setDefaultMathAttribute(Mo.ATTR_ACCENT, Constants.FALSE);
195 this.opDict = OperatorDictionary.getInstance();
196 }
197
198
199 @Override
200 protected Node newNode() {
201 return new Mo();
202 }
203
204
205
206
207
208
209 private float getLspaceAsFloat(final LayoutContext now) {
210 if (((Integer) now.getParameter(Parameter.SCRIPTLEVEL)) > 0) {
211 return 0.0f;
212 } else {
213 return AttributesHelper.convertSizeToPt(this.getLspace(), now,
214 AttributesHelper.PT);
215 }
216 }
217
218
219
220
221
222
223
224 public float getLargeOpCorrector(final LayoutContext now) {
225 if (Display.BLOCK.equals(now.getParameter(Parameter.DISPLAY))) {
226 return Mo.LARGEOP_CORRECTOR_BLOCK;
227 } else {
228 return Mo.LARGEOP_CORRECTOR_INLINE;
229 }
230 }
231
232
233
234
235
236
237 private float getRspaceAsFloat(final LayoutContext now) {
238 if (((Integer) now.getParameter(Parameter.SCRIPTLEVEL)) > 0) {
239 return 0.0f;
240 } else {
241 return AttributesHelper.convertSizeToPt(this.getRspace(), now,
242 AttributesHelper.PT);
243 }
244 }
245
246 private boolean isFence() {
247 return Boolean.parseBoolean(this.getFence());
248 }
249
250
251
252
253
254
255
256 public void setMaxsize(final String maxsize) {
257 this.setAttribute(Mo.ATTR_MAXSIZE, maxsize);
258 }
259
260
261
262
263
264
265 public String getMaxsize() {
266 return this.getMathAttribute(Mo.ATTR_MAXSIZE);
267 }
268
269
270
271
272
273
274
275 public void setMinsize(final String minsize) {
276 this.setAttribute(Mo.ATTR_MINSIZE, minsize);
277 }
278
279
280
281
282
283
284 public String getMinsize() {
285 return this.getMathAttribute(Mo.ATTR_MINSIZE);
286 }
287
288 private boolean isVerticalDelimeter() {
289 return this.getText().length() == 1
290 && (Mo.VER_DELIMITERS.indexOf(this.getText().charAt(0)) >= 0 || this
291 .isFence());
292 }
293
294 private boolean isHorizontalDelimeter() {
295 return this.getText().length() == 1
296 && (Mo.HOR_DELIMITERS.indexOf(this.getText().charAt(0)) >= 0);
297 }
298
299 private TextLayout produceUnstrechtedLayout(final Graphics2D g,
300 final LayoutContext now) {
301 assert g != null : "Graphics2d is null in produceUnstrechtedLayout";
302 float fontSizeInPoint = GraphicsSupport.getFontsizeInPoint(now);
303 if (Boolean.parseBoolean(this.getLargeop())) {
304 fontSizeInPoint *= this.getLargeOpCorrector(now);
305 }
306
307 final String theText = this.getText();
308 final AttributedString aString = StringUtil
309 .convertStringtoAttributedString(theText, this
310 .getMathvariantAsVariant(), fontSizeInPoint, now);
311 final TextLayout theLayout = StringUtil
312 .createTextLayoutFromAttributedString(g, aString, now);
313 return theLayout;
314 }
315
316
317 @Override
318 public void changeHook() {
319 super.changeHook();
320 if (!this.inChangeHook) {
321 this.inChangeHook = true;
322 this.detectFormParameter();
323 this
324 .loadAttributeFromDictionary(Mo.ATTR_LARGEOP,
325 Constants.FALSE);
326 this.loadAttributeFromDictionary(Mo.ATTR_SYMMETRIC,
327 Constants.TRUE);
328 this.loadAttributeFromDictionary(Mo.ATTR_STRETCHY,
329 Constants.FALSE);
330 this.loadAttributeFromDictionary(Mo.ATTR_FENCE, Constants.FALSE);
331 this.loadAttributeFromDictionary(Mo.ATTR_LSPACE,
332 AttributesHelper.THICKMATHSPACE);
333 this.loadAttributeFromDictionary(Mo.ATTR_RSPACE,
334 AttributesHelper.THICKMATHSPACE);
335 this.loadAttributeFromDictionary(Mo.ATTR_MOVABLELIMITS,
336 Constants.FALSE);
337
338
339 JEuclidElement parent = this.getParent();
340 while (parent != null) {
341 if (parent instanceof EventTarget) {
342 ((EventTarget) parent).addEventListener(
343 "DOMSubtreeModified", this, false);
344 }
345 if (parent instanceof Mrow) {
346 parent = null;
347 } else {
348 parent = parent.getParent();
349 }
350 }
351
352 if (this.isFence()) {
353 this
354 .setDefaultMathAttribute(Mo.ATTR_STRETCHY,
355 Constants.TRUE);
356 }
357 final CustomEvent evt = new DOMCustomEvent();
358 evt.initCustomEventNS(null, Mo.MOEVENT, true, false, null);
359 this.dispatchEvent(evt);
360 this.inChangeHook = false;
361 }
362 }
363
364 private void loadAttributeFromDictionary(final String attrname,
365 final String defvalue) {
366 String attr;
367 try {
368 attr = this.opDict.getDefaultAttributeValue(this.getText(), this
369 .getForm(), attrname);
370 } catch (final UnknownAttributeException e) {
371 attr = defvalue;
372 }
373 if (attr.equals(OperatorDictionary.VALUE_UNKNOWN)) {
374 attr = defvalue;
375 }
376 this.setDefaultMathAttribute(attrname, attr);
377
378 }
379
380 private void detectFormParameter() {
381 final String form;
382 final JEuclidElement parent = this.getParent();
383 if (parent != null && (parent instanceof Mrow)) {
384 final int index = parent.getIndexOfMathElement(this);
385 if (index == 0 && parent.getMathElementCount() > 0) {
386 form = OperatorDictionary.FORM_PREFIX;
387 } else {
388 if (index == (parent.getMathElementCount() - 1)
389 && parent.getMathElementCount() > 0) {
390 form = OperatorDictionary.FORM_POSTFIX;
391 } else {
392 form = OperatorDictionary.FORM_INFIX;
393 }
394 }
395 } else {
396 form = OperatorDictionary.FORM_INFIX;
397 }
398 this.setDefaultMathAttribute(Mo.ATTR_FORM, form);
399
400 }
401
402
403 public String getLargeop() {
404 return this.getMathAttribute(Mo.ATTR_LARGEOP);
405 }
406
407
408 public String getLspace() {
409 return this.getMathAttribute(Mo.ATTR_LSPACE);
410 }
411
412
413 public String getMovablelimits() {
414 final String wrongAttr = this.getMathAttribute(Mo.ATTR_MOVEABLEWRONG,
415 false);
416 if (wrongAttr == null) {
417 return this.getMathAttribute(Mo.ATTR_MOVABLELIMITS);
418 } else {
419 return wrongAttr;
420 }
421 }
422
423
424 public String getRspace() {
425 return this.getMathAttribute(Mo.ATTR_RSPACE);
426 }
427
428
429 public void setAccent(final String accent) {
430 this.setAttribute(Mo.ATTR_ACCENT, accent);
431 }
432
433
434 public void setFence(final String fence) {
435 this.setAttribute(Mo.ATTR_FENCE, fence);
436 }
437
438
439 public void setForm(final String form) {
440 this.setAttribute(Mo.ATTR_FORM, form);
441 }
442
443
444 public void setLargeop(final String largeop) {
445 this.setAttribute(Mo.ATTR_LARGEOP, largeop);
446 }
447
448
449 public void setLspace(final String lspace) {
450 this.setAttribute(Mo.ATTR_LSPACE, lspace);
451 }
452
453
454 public void setMovablelimits(final String movablelimits) {
455 this.setAttribute(Mo.ATTR_MOVABLELIMITS, movablelimits);
456 }
457
458
459 public void setRspace(final String rspace) {
460 this.setAttribute(Mo.ATTR_RSPACE, rspace);
461 }
462
463
464 public void setSeparator(final String separator) {
465 this.setAttribute(Mo.ATTR_SEPARATOR, separator);
466 }
467
468
469 public void setStretchy(final String stretchy) {
470 this.setAttribute(Mo.ATTR_STRETCHY, stretchy);
471 }
472
473
474 public void setSymmetric(final String symmetric) {
475 this.setAttribute(Mo.ATTR_SYMMETRIC, symmetric);
476 }
477
478
479 public String getFence() {
480 return this.getMathAttribute(Mo.ATTR_FENCE);
481 }
482
483
484 public String getForm() {
485 return this.getMathAttribute(Mo.ATTR_FORM);
486 }
487
488
489 public String getSeparator() {
490 return this.getMathAttribute(Mo.ATTR_SEPARATOR);
491 }
492
493
494 public String getStretchy() {
495 return this.getMathAttribute(Mo.ATTR_STRETCHY);
496 }
497
498
499 public String getAccent() {
500 return this.getMathAttribute(Mo.ATTR_ACCENT);
501 }
502
503
504 public String getSymmetric() {
505 return this.getMathAttribute(Mo.ATTR_SYMMETRIC);
506 }
507
508
509 @Override
510 public void layoutStage1(final LayoutView view, final LayoutInfo info,
511 final LayoutStage childMinStage, final LayoutContext context) {
512 final LayoutContext now = this.applyLocalAttributesToContext(context);
513 final Graphics2D g = view.getGraphics();
514 final TextLayout t = this.produceUnstrechtedLayout(g, now);
515
516 final StringUtil.TextLayoutInfo tli = StringUtil.getTextLayoutInfo(t,
517 true);
518 final float ascent = tli.getAscent();
519 final float descent = tli.getDescent();
520 final float xOffset = tli.getOffset();
521 final float contentWidth = tli.getWidth() + xOffset;
522 final JEuclidElement parent = this.getParent();
523 float lspace = this.getLspaceAsFloat(now);
524 float rspace = this.getRspaceAsFloat(now);
525 if ((parent != null) && (parent.hasChildPostscripts(this, context))) {
526 rspace = 0.0f;
527 } else {
528 rspace = this.getRspaceAsFloat(now);
529 }
530 if ((parent != null) && (parent.hasChildPrescripts(this))) {
531 lspace = 0.0f;
532 } else {
533 lspace = this.getLspaceAsFloat(now);
534 }
535
536 info.setAscentHeight(ascent, LayoutStage.STAGE1);
537 info.setDescentHeight(descent, LayoutStage.STAGE1);
538 info.setHorizontalCenterOffset(lspace + contentWidth / 2.0f,
539 LayoutStage.STAGE1);
540 info.setWidth(lspace + contentWidth + rspace, LayoutStage.STAGE1);
541 if (Boolean.parseBoolean(this.getStretchy())
542 || this.isVerticalDelimeter() || this.isHorizontalDelimeter()) {
543 info.setLayoutStage(LayoutStage.STAGE1);
544 } else {
545 info.setGraphicsObject(new TextObject(t,
546 lspace + tli.getOffset(), 0, null, (Color) now
547 .getParameter(Parameter.MATHCOLOR)));
548 info.setLayoutStage(LayoutStage.STAGE2);
549 }
550 }
551
552
553 @Override
554 public void layoutStage2(final LayoutView view, final LayoutInfo info,
555 final LayoutContext context) {
556 final LayoutContext now = this.applyLocalAttributesToContext(context);
557
558 final Graphics2D g = view.getGraphics();
559 final TextLayout t = this.produceUnstrechtedLayout(g, now);
560
561 final float calcScaleY;
562 final float calcScaleX;
563 final float calcBaselineShift;
564 final boolean stretchVertically = this.isVerticalDelimeter();
565 JEuclidElement parent = this.getParent();
566 while (((parent instanceof MathMLUnderOverElement) && stretchVertically)
567 || ((parent instanceof Mrow) && (parent.getMathElementCount() == 1))) {
568 parent = parent.getParent();
569 }
570 final LayoutInfo parentInfo = view.getInfo(parent);
571 final TextLayoutInfo textLayoutInfo = StringUtil.getTextLayoutInfo(t,
572 true);
573 if (stretchVertically) {
574 final float[] yf = this.calcYScaleFactorAndBaselineShift(info,
575 parentInfo, textLayoutInfo, now);
576 calcScaleY = yf[0];
577 calcBaselineShift = yf[1];
578 } else {
579 calcScaleY = 1.0f;
580 calcBaselineShift = 0.0f;
581 }
582 calcScaleX = this.calcXScaleFactor(info, parentInfo, textLayoutInfo);
583
584 info.setGraphicsObject(new TextObject(t, this.getLspaceAsFloat(now)
585 + textLayoutInfo.getOffset() * calcScaleX, calcBaselineShift,
586 AffineTransform.getScaleInstance(calcScaleX, calcScaleY),
587 (Color) now.getParameter(Parameter.MATHCOLOR)));
588 info.setLayoutStage(LayoutStage.STAGE2);
589 }
590
591 private float calcXScaleFactor(final LayoutInfo info,
592 final LayoutInfo parentInfo, final TextLayoutInfo textLayoutInfo) {
593 final float calcScaleX;
594 final float stretchWidth = parentInfo.getStretchWidth();
595 if ((this.isHorizontalDelimeter()) && (stretchWidth > 0.0f)) {
596 final float realwidth = textLayoutInfo.getWidth();
597 if (realwidth > 0) {
598 calcScaleX = stretchWidth / realwidth;
599 info.setWidth(stretchWidth, LayoutStage.STAGE2);
600 info.setHorizontalCenterOffset(stretchWidth / 2.0f,
601 LayoutStage.STAGE2);
602 } else {
603 calcScaleX = 1.0f;
604 }
605 } else {
606 calcScaleX = 1.0f;
607 }
608 return calcScaleX;
609 }
610
611 private float[] calcYScaleFactorAndBaselineShift(final LayoutInfo info,
612 final LayoutInfo parentInfo, final TextLayoutInfo textLayoutInfo,
613 final LayoutContext now) {
614 final float calcScaleY;
615 final float calcBaselineShift;
616 final float targetNAscent = parentInfo.getStretchAscent();
617 final float targetNDescent = parentInfo.getStretchDescent();
618
619 final float targetNHeight = targetNAscent + targetNDescent;
620
621 final float realDescent = textLayoutInfo.getDescent();
622 final float realHeight = textLayoutInfo.getAscent() + realDescent;
623
624
625 final float maxSize = AttributesHelper.parseRelativeSize(this
626 .getMaxsize(), now, realHeight);
627 final float minSize = AttributesHelper.parseRelativeSize(this
628 .getMinsize(), now, realHeight);
629 final float targetHeight = Math.max(Math.min(targetNHeight, maxSize),
630 minSize);
631 final float targetDescent = targetHeight / targetNHeight
632 * (targetNHeight / 2.0f)
633 - (targetNHeight / 2.0f - targetNDescent);
634
635 if (realHeight > 0.0f) {
636 calcScaleY = targetHeight / realHeight;
637 } else {
638 calcScaleY = 1.0f;
639 }
640 final float realDescentScaled = realDescent * calcScaleY;
641 calcBaselineShift = targetDescent - realDescentScaled;
642
643 info.setDescentHeight(targetDescent, LayoutStage.STAGE2);
644 info
645 .setAscentHeight(targetHeight - targetDescent,
646 LayoutStage.STAGE2);
647 return new float[] { calcScaleY, calcBaselineShift };
648 }
649
650
651 public void handleEvent(final Event evt) {
652 this.changeHook();
653 }
654 }