View Javadoc

1   /*
2    * Copyright 2002 - 2007 JEuclid, http://jeuclid.sf.net
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  /* $Id: Mo.java,v e2b3e25686bf 2009/09/29 19:14:25 max $ */
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.OperatorDictionary2;
38  import net.sourceforge.jeuclid.elements.support.operatordict.UnknownAttributeException;
39  import net.sourceforge.jeuclid.elements.support.text.StringUtil;
40  import net.sourceforge.jeuclid.elements.support.text.StringUtil.TextLayoutInfo;
41  import net.sourceforge.jeuclid.layout.LayoutInfo;
42  import net.sourceforge.jeuclid.layout.LayoutStage;
43  import net.sourceforge.jeuclid.layout.LayoutView;
44  import net.sourceforge.jeuclid.layout.TextObject;
45  
46  import org.apache.batik.dom.AbstractDocument;
47  import org.apache.batik.dom.events.DOMCustomEvent;
48  import org.w3c.dom.Attr;
49  import org.w3c.dom.Node;
50  import org.w3c.dom.events.CustomEvent;
51  import org.w3c.dom.events.Event;
52  import org.w3c.dom.events.EventListener;
53  import org.w3c.dom.events.EventTarget;
54  import org.w3c.dom.mathml.MathMLOperatorElement;
55  import org.w3c.dom.mathml.MathMLScriptElement;
56  import org.w3c.dom.mathml.MathMLUnderOverElement;
57  
58  /**
59   * This class presents a math operator, like "(" or "*".
60   * 
61   * @version $Revision: e2b3e25686bf $
62   */
63  
64  // CHECKSTYLE:OFF
65  // Class Fan-out is to high. However, this is required due to complexity of
66  // mo.
67  public final class Mo extends AbstractJEuclidElement implements
68          MathMLOperatorElement, EventListener {
69      // CHECKSTYLE:ON
70  
71      /** Attribute for form. */
72      public static final String ATTR_FORM = "form";
73  
74      /** Attribute for separator. */
75      public static final String ATTR_SEPARATOR = "separator";
76  
77      /** Attribute for lspace. */
78      public static final String ATTR_LSPACE = "lspace";
79  
80      /** Attribute for rspace. */
81      public static final String ATTR_RSPACE = "rspace";
82  
83      /** Attribute for min size. */
84      public static final String ATTR_MINSIZE = "minsize";
85  
86      /** Attribute for max size. */
87      public static final String ATTR_MAXSIZE = "maxsize";
88  
89      /** Wrong attribute name for movable limits. */
90      public static final String ATTR_MOVEABLEWRONG = "moveablelimits";
91  
92      /** Attribute for movable limits. */
93      public static final String ATTR_MOVABLELIMITS = "movablelimits";
94  
95      /** Attribute for accent. */
96      public static final String ATTR_ACCENT = "accent";
97  
98      /**
99       * The XML element from this class.
100      */
101     public static final String ELEMENT = "mo";
102 
103     /**
104      * Multiplier for increasing size of mo with attribute largop = true.
105      */
106     public static final float LARGEOP_CORRECTOR_INLINE = (float) 1.2;
107 
108     /**
109      * Multiplier for increasing size of mo with attribute largop = true.
110      */
111     public static final float LARGEOP_CORRECTOR_BLOCK = (float) 1.5;
112 
113     /**
114      * Attribute name of the stretchy property.
115      */
116     public static final String ATTR_STRETCHY = "stretchy";
117 
118     /** JEuclid extension to stretchy: stretch horizontal only. */
119     public static final String VALUE_STRETCHY_HORIZONTAL = "horizontal";
120 
121     /** JEuclid extension to stretchy: stretch vertical only. */
122     public static final String VALUE_STRETCHY_VERTICAL = "vertical";
123     /**
124      * Attribute name of the largeop property.
125      */
126     public static final String ATTR_LARGEOP = "largeop";
127 
128     /**
129      * Attribute name of the symmetric property.
130      */
131     public static final String ATTR_SYMMETRIC = "symmetric";
132 
133     /**
134      * Attribute name of the fence property.
135      */
136     public static final String ATTR_FENCE = "fence";
137 
138     /**
139      * Event name for operator events.
140      */
141     public static final String MOEVENT = "MOEvent";
142 
143     private static final long serialVersionUID = 1L;
144 
145     private final OperatorDictionary opDict;
146 
147     private boolean inChangeHook;
148 
149     /**
150      * Logger for this class
151      */
152     // unused
153     // private static final Log LOGGER =
154     // LogFactory.getLog(MathOperator.class);
155     /**
156      * Default constructor. Sets MathML Namespace.
157      * 
158      * @param qname
159      *            Qualified name.
160      * @param odoc
161      *            Owner Document.
162      */
163     public Mo(final String qname, final AbstractDocument odoc) {
164         super(qname, odoc);
165 
166         this.setDefaultMathAttribute(Mo.ATTR_FORM,
167                 OperatorDictionary.FORM_INFIX);
168         this.setDefaultMathAttribute(Mo.ATTR_FENCE, Constants.FALSE);
169         this.setDefaultMathAttribute(Mo.ATTR_SEPARATOR, Constants.FALSE);
170         this.setDefaultMathAttribute(Mo.ATTR_LSPACE,
171                 AttributesHelper.THICKMATHSPACE);
172         this.setDefaultMathAttribute(Mo.ATTR_RSPACE,
173                 AttributesHelper.THICKMATHSPACE);
174         this.setDefaultMathAttribute(Mo.ATTR_STRETCHY, Constants.FALSE);
175         this.setDefaultMathAttribute(Mo.ATTR_SYMMETRIC, Constants.TRUE);
176         this
177                 .setDefaultMathAttribute(Mo.ATTR_MAXSIZE,
178                         AttributesHelper.INFINITY);
179         this.setDefaultMathAttribute(Mo.ATTR_MINSIZE, "1");
180         this.setDefaultMathAttribute(Mo.ATTR_LARGEOP, Constants.FALSE);
181         this.setDefaultMathAttribute(Mo.ATTR_MOVABLELIMITS, Constants.FALSE);
182         this.setDefaultMathAttribute(Mo.ATTR_ACCENT, Constants.FALSE);
183         this.opDict = OperatorDictionary2.getInstance();
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     protected Node newNode() {
189         return new Mo(this.nodeName, this.ownerDocument);
190     }
191 
192     /**
193      * Gets value of lspace property of the operator.
194      * 
195      * @return Flag of lspace property.
196      */
197     private float getLspaceAsFloat(final LayoutContext now) {
198         if (((Integer) now.getParameter(Parameter.SCRIPTLEVEL)) > 0) {
199             return 0.0f;
200         } else {
201             return AttributesHelper.convertSizeToPt(this.getLspace(), now,
202                     AttributesHelper.PT);
203         }
204     }
205 
206     /**
207      * @param now
208      *            applied layout context.
209      * @return Multiplier for increasing size of mo whith attribute largop =
210      *         true
211      */
212     public float getLargeOpCorrector(final LayoutContext now) {
213         if (Display.BLOCK.equals(now.getParameter(Parameter.DISPLAY))) {
214             return Mo.LARGEOP_CORRECTOR_BLOCK;
215         } else {
216             return Mo.LARGEOP_CORRECTOR_INLINE;
217         }
218     }
219 
220     /**
221      * Gets value of rspace property of the operator.
222      * 
223      * @return Flag of rspace property.
224      */
225     private float getRspaceAsFloat(final LayoutContext now) {
226         if (((Integer) now.getParameter(Parameter.SCRIPTLEVEL)) > 0) {
227             return 0.0f;
228         } else {
229             return AttributesHelper.convertSizeToPt(this.getRspace(), now,
230                     AttributesHelper.PT);
231         }
232     }
233 
234     private boolean isFence() {
235         return Boolean.parseBoolean(this.getFence());
236     }
237 
238     /**
239      * Sets value of maxsize property.
240      * 
241      * @param maxsize
242      *            Maxsize value.
243      */
244     public void setMaxsize(final String maxsize) {
245         this.setAttribute(Mo.ATTR_MAXSIZE, maxsize);
246     }
247 
248     /**
249      * Gets value of maxsize property.
250      * 
251      * @return Maxsize value.
252      */
253     public String getMaxsize() {
254         return this.getMathAttribute(Mo.ATTR_MAXSIZE);
255     }
256 
257     /**
258      * Sets value of minsize property.
259      * 
260      * @param minsize
261      *            Minsize value.
262      */
263     public void setMinsize(final String minsize) {
264         this.setAttribute(Mo.ATTR_MINSIZE, minsize);
265     }
266 
267     /**
268      * Gets value of minsize property.
269      * 
270      * @return Minsize value.
271      */
272     public String getMinsize() {
273         return this.getMathAttribute(Mo.ATTR_MINSIZE);
274     }
275 
276     private TextLayout produceUnstrechtedLayout(final Graphics2D g,
277             final LayoutContext now) {
278         assert g != null : "Graphics2d is null in produceUnstrechtedLayout";
279         float fontSizeInPoint = GraphicsSupport.getFontsizeInPoint(now);
280         if (Boolean.parseBoolean(this.getLargeop())) {
281             fontSizeInPoint *= this.getLargeOpCorrector(now);
282         }
283 
284         final String theText = this.getText();
285         final AttributedString aString = StringUtil
286                 .convertStringtoAttributedString(theText, this
287                         .getMathvariantAsVariant(), fontSizeInPoint, now);
288         final TextLayout theLayout = StringUtil
289                 .createTextLayoutFromAttributedString(g, aString, now);
290         return theLayout;
291     }
292 
293     /** {@inheritDoc} */
294     @Override
295     public void changeHook() {
296         super.changeHook();
297         if (!this.inChangeHook) {
298             this.inChangeHook = true;
299             this.detectFormParameter();
300             this.loadAttributeFromDictionary(Mo.ATTR_LARGEOP, Constants.FALSE);
301             this.loadAttributeFromDictionary(Mo.ATTR_SYMMETRIC, Constants.TRUE);
302             this.loadAttributeFromDictionary(Mo.ATTR_STRETCHY, Constants.FALSE);
303             this.loadAttributeFromDictionary(Mo.ATTR_FENCE, Constants.FALSE);
304             this.loadAttributeFromDictionary(Mo.ATTR_LSPACE,
305                     AttributesHelper.THICKMATHSPACE);
306             this.loadAttributeFromDictionary(Mo.ATTR_RSPACE,
307                     AttributesHelper.THICKMATHSPACE);
308             this.loadAttributeFromDictionary(Mo.ATTR_MOVABLELIMITS,
309                     Constants.FALSE);
310             // TODO: Load all.
311             this.registerWithParentsForEvents();
312             if (this.isFence()) {
313                 this.setDefaultMathAttribute(Mo.ATTR_STRETCHY,
314                         Mo.VALUE_STRETCHY_VERTICAL);
315             }
316             final CustomEvent evt = new DOMCustomEvent();
317             evt.initCustomEventNS(null, Mo.MOEVENT, true, false, null);
318             this.dispatchEvent(evt);
319             this.inChangeHook = false;
320         }
321     }
322 
323     private void registerWithParentsForEvents() {
324         JEuclidElement parent = this.getParent();
325         while (parent != null) {
326             if (parent instanceof EventTarget) {
327                 ((EventTarget) parent).addEventListener("DOMSubtreeModified",
328                         this, false);
329             }
330             if ((parent instanceof Mrow) && (parent.getMathElementCount() > 1)) {
331                 parent = null;
332             } else {
333                 parent = parent.getParent();
334             }
335         }
336     }
337 
338     private void loadAttributeFromDictionary(final String attrname,
339             final String defvalue) {
340         String attr;
341         try {
342             attr = this.opDict.getDefaultAttributeValue(this.getText(), this
343                     .getForm(), attrname);
344         } catch (final UnknownAttributeException e) {
345             attr = defvalue;
346         }
347         if (attr.equals(OperatorDictionary.VALUE_UNKNOWN)) {
348             attr = defvalue;
349         }
350         this.setDefaultMathAttribute(attrname, attr);
351 
352     }
353 
354     private void detectFormParameter() {
355         final String form;
356         final JEuclidElement parent = this.getParent();
357         if ((parent != null) && (parent instanceof Mrow)) {
358             final int index = parent.getIndexOfMathElement(this);
359             if ((index == 0) && (parent.getMathElementCount() > 0)) {
360                 form = OperatorDictionary.FORM_PREFIX;
361             } else {
362                 if ((index == (parent.getMathElementCount() - 1))
363                         && (parent.getMathElementCount() > 0)) {
364                     form = OperatorDictionary.FORM_POSTFIX;
365                 } else {
366                     form = OperatorDictionary.FORM_INFIX;
367                 }
368             }
369         } else {
370             form = OperatorDictionary.FORM_INFIX;
371         }
372         this.setDefaultMathAttribute(Mo.ATTR_FORM, form);
373         // TODO: Exception for embellished operators
374     }
375 
376     /** {@inheritDoc} */
377     public String getLargeop() {
378         return this.getMathAttribute(Mo.ATTR_LARGEOP);
379     }
380 
381     /** {@inheritDoc} */
382     public String getLspace() {
383         return this.getMathAttribute(Mo.ATTR_LSPACE);
384     }
385 
386     /** {@inheritDoc} */
387     public String getMovablelimits() {
388         final String wrongAttr = this.getMathAttribute(Mo.ATTR_MOVEABLEWRONG,
389                 false);
390         if (wrongAttr == null) {
391             return this.getMathAttribute(Mo.ATTR_MOVABLELIMITS);
392         } else {
393             return wrongAttr;
394         }
395     }
396 
397     /** {@inheritDoc} */
398     public String getRspace() {
399         return this.getMathAttribute(Mo.ATTR_RSPACE);
400     }
401 
402     /** {@inheritDoc} */
403     public void setAccent(final String accent) {
404         this.setAttribute(Mo.ATTR_ACCENT, accent);
405     }
406 
407     /** {@inheritDoc} */
408     public void setFence(final String fence) {
409         this.setAttribute(Mo.ATTR_FENCE, fence);
410     }
411 
412     /** {@inheritDoc} */
413     public void setForm(final String form) {
414         this.setAttribute(Mo.ATTR_FORM, form);
415     }
416 
417     /** {@inheritDoc} */
418     public void setLargeop(final String largeop) {
419         this.setAttribute(Mo.ATTR_LARGEOP, largeop);
420     }
421 
422     /** {@inheritDoc} */
423     public void setLspace(final String lspace) {
424         this.setAttribute(Mo.ATTR_LSPACE, lspace);
425     }
426 
427     /** {@inheritDoc} */
428     public void setMovablelimits(final String movablelimits) {
429         this.setAttribute(Mo.ATTR_MOVABLELIMITS, movablelimits);
430     }
431 
432     /** {@inheritDoc} */
433     public void setRspace(final String rspace) {
434         this.setAttribute(Mo.ATTR_RSPACE, rspace);
435     }
436 
437     /** {@inheritDoc} */
438     public void setSeparator(final String separator) {
439         this.setAttribute(Mo.ATTR_SEPARATOR, separator);
440     }
441 
442     /** {@inheritDoc} */
443     public void setStretchy(final String stretchy) {
444         this.setAttribute(Mo.ATTR_STRETCHY, stretchy);
445     }
446 
447     /** {@inheritDoc} */
448     public void setSymmetric(final String symmetric) {
449         this.setAttribute(Mo.ATTR_SYMMETRIC, symmetric);
450     }
451 
452     /** {@inheritDoc} */
453     public String getFence() {
454         return this.getMathAttribute(Mo.ATTR_FENCE);
455     }
456 
457     /** {@inheritDoc} */
458     public String getForm() {
459         return this.getMathAttribute(Mo.ATTR_FORM);
460     }
461 
462     /** {@inheritDoc} */
463     public String getSeparator() {
464         return this.getMathAttribute(Mo.ATTR_SEPARATOR);
465     }
466 
467     /**
468      * Retrieves the JEuclid specific extension of the stretch attribute. This
469      * method may return {@link Constants#TRUE}, {@link Constants#FALSE},
470      * {@link #VALUE_STRETCHY_HORIZONTAL}, {@link #VALUE_STRETCHY_VERTICAL}, or
471      * null if no stretchy attribute is set.
472      * 
473      * @return an JEuclid stretchy attribute.
474      */
475     public String getExtendedStretchy() {
476         final String retVal;
477         final Attr attr = this.getAttributeNodeNS(Constants.NS_JEUCLID_EXT,
478                 Mo.ATTR_STRETCHY);
479         if (attr == null) {
480             retVal = this.getMathAttribute(Mo.ATTR_STRETCHY);
481         } else {
482             retVal = attr.getValue().trim();
483         }
484         return retVal;
485     }
486 
487     /** {@inheritDoc} */
488     public String getStretchy() {
489         final String stretchVal = this.getExtendedStretchy();
490         if ((Mo.VALUE_STRETCHY_HORIZONTAL.equalsIgnoreCase(stretchVal))
491                 || (Mo.VALUE_STRETCHY_VERTICAL.equalsIgnoreCase(stretchVal))) {
492             return Constants.TRUE;
493         } else {
494             return stretchVal;
495         }
496     }
497 
498     private boolean isStretchyHorizontal(final String stretchValue) {
499         return Mo.VALUE_STRETCHY_HORIZONTAL.equalsIgnoreCase(stretchValue)
500                 || Boolean.parseBoolean(stretchValue);
501     }
502 
503     private boolean isStretchyVertical(final String stretchValue) {
504         return Mo.VALUE_STRETCHY_VERTICAL.equalsIgnoreCase(stretchValue)
505                 || Boolean.parseBoolean(stretchValue);
506     }
507 
508     private boolean isStretchy() {
509         final String stretchValue = this.getExtendedStretchy();
510         return this.isStretchyHorizontal(stretchValue)
511                 || this.isStretchyVertical(stretchValue);
512     }
513 
514     /** {@inheritDoc} */
515     public String getAccent() {
516         return this.getMathAttribute(Mo.ATTR_ACCENT);
517     }
518 
519     /** {@inheritDoc} */
520     public String getSymmetric() {
521         return this.getMathAttribute(Mo.ATTR_SYMMETRIC);
522     }
523 
524     private boolean isSymmetric() {
525         return Boolean.parseBoolean(this.getSymmetric());
526     }
527 
528     /** {@inheritDoc} */
529     @Override
530     public void layoutStage1(final LayoutView view, final LayoutInfo info,
531             final LayoutStage childMinStage, final LayoutContext context) {
532         final LayoutContext now = this.applyLocalAttributesToContext(context);
533         final Graphics2D g = view.getGraphics();
534         final TextLayout t = this.produceUnstrechtedLayout(g, now);
535 
536         final StringUtil.TextLayoutInfo tli = StringUtil.getTextLayoutInfo(t,
537                 true);
538         final float ascent = tli.getAscent();
539         final float descent = tli.getDescent();
540         final float xOffset = tli.getOffset();
541         final float contentWidth = tli.getWidth() + xOffset;
542         final JEuclidElement parent = this.getParent();
543         float lspace = this.getLspaceAsFloat(now);
544         float rspace = this.getRspaceAsFloat(now);
545         if ((parent != null) && (parent.hasChildPostscripts(this, context))) {
546             rspace = 0.0f;
547         } else {
548             rspace = this.getRspaceAsFloat(now);
549         }
550         if ((parent != null) && (parent.hasChildPrescripts(this))) {
551             lspace = 0.0f;
552         } else {
553             lspace = this.getLspaceAsFloat(now);
554         }
555 
556         info.setAscentHeight(ascent, LayoutStage.STAGE1);
557         info.setDescentHeight(descent, LayoutStage.STAGE1);
558         info.setHorizontalCenterOffset(lspace + contentWidth / 2.0f,
559                 LayoutStage.STAGE1);
560         info.setWidth(lspace + contentWidth + rspace, LayoutStage.STAGE1);
561         if (this.isStretchy()) {
562             info.setLayoutStage(LayoutStage.STAGE1);
563             info.setStretchAscent(0.0f);
564             info.setStretchDescent(0.0f);
565         } else {
566             info.setGraphicsObject(new TextObject(t, lspace + tli.getOffset(),
567                     0, null, (Color) now.getParameter(Parameter.MATHCOLOR)));
568             info.setLayoutStage(LayoutStage.STAGE2);
569         }
570     }
571 
572     /** {@inheritDoc} */
573     @Override
574     public void layoutStage2(final LayoutView view, final LayoutInfo info,
575             final LayoutContext context) {
576         final LayoutContext now = this.applyLocalAttributesToContext(context);
577 
578         final Graphics2D g = view.getGraphics();
579         final TextLayout t = this.produceUnstrechtedLayout(g, now);
580 
581         final float calcScaleY;
582         final float calcScaleX;
583         final float calcBaselineShift;
584         final String stretchValue = this.getExtendedStretchy();
585         boolean stretchVertically = this.isStretchyVertical(stretchValue);
586         final boolean stretchHorizontally = this
587                 .isStretchyHorizontal(stretchValue);
588 
589         JEuclidElement horizParent = null;
590         JEuclidElement parent = this;
591         JEuclidElement last;
592         boolean cont;
593         do {
594             last = parent;
595             parent = parent.getParent();
596             cont = false;
597             if ((parent instanceof Mrow) && (parent.getMathElementCount() == 1)) {
598                 // Ignore single element Mrows.
599                 cont = true;
600             } else if ((parent instanceof MathMLUnderOverElement)
601                     || (parent instanceof MathMLScriptElement)) {
602                 // Special Treatment for UnderOverElements to match stretchVert1
603                 // test.
604                 final boolean isBase;
605                 if (parent instanceof MathMLUnderOverElement) {
606                     final MathMLUnderOverElement munderover = (MathMLUnderOverElement) parent;
607                     isBase = munderover.getBase() == last;
608                 } else {
609                     final MathMLScriptElement munderover = (MathMLScriptElement) parent;
610                     isBase = munderover.getBase() == last;
611                 }
612                 if (!isBase) {
613                     stretchVertically = false;
614                 }
615                 horizParent = parent;
616                 cont = true;
617             }
618         } while (cont);
619         if (horizParent == null) {
620             horizParent = parent;
621         }
622 
623         final LayoutInfo parentInfo = view.getInfo(parent);
624         final TextLayoutInfo textLayoutInfo = StringUtil.getTextLayoutInfo(t,
625                 true);
626         if (parentInfo == null) {
627             calcScaleX = 1.0f;
628             calcScaleY = 1.0f;
629             calcBaselineShift = 0.0f;
630         } else {
631             if (stretchVertically) {
632                 final float[] yf = this.calcYScaleFactorAndBaselineShift(info,
633                         parentInfo, textLayoutInfo, now, g);
634                 calcScaleY = yf[0];
635                 calcBaselineShift = yf[1];
636             } else {
637                 calcScaleY = 1.0f;
638                 calcBaselineShift = 0.0f;
639             }
640             if (stretchHorizontally) {
641                 calcScaleX = this.calcXScaleFactor(info, view
642                         .getInfo(horizParent), textLayoutInfo);
643             } else {
644                 calcScaleX = 1.0f;
645             }
646         }
647         info.setGraphicsObject(new TextObject(t, this.getLspaceAsFloat(now)
648                 + textLayoutInfo.getOffset() * calcScaleX, calcBaselineShift,
649                 AffineTransform.getScaleInstance(calcScaleX, calcScaleY),
650                 (Color) now.getParameter(Parameter.MATHCOLOR)));
651         info.setLayoutStage(LayoutStage.STAGE2);
652     }
653 
654     private float calcXScaleFactor(final LayoutInfo info,
655             final LayoutInfo parentInfo, final TextLayoutInfo textLayoutInfo) {
656         final float calcScaleX;
657         final float rstretchWidth = parentInfo.getStretchWidth();
658         if (rstretchWidth > 0.0f) {
659             final float realwidth = textLayoutInfo.getWidth();
660             if (realwidth > 0) {
661                 final float stretchWidth = Math.max(realwidth, rstretchWidth);
662                 calcScaleX = stretchWidth / realwidth;
663                 info.setWidth(stretchWidth, LayoutStage.STAGE2);
664                 info.setHorizontalCenterOffset(stretchWidth / 2.0f,
665                         LayoutStage.STAGE2);
666             } else {
667                 calcScaleX = 1.0f;
668             }
669         } else {
670             calcScaleX = 1.0f;
671         }
672         return calcScaleX;
673     }
674 
675     private float[] calcYScaleFactorAndBaselineShift(final LayoutInfo info,
676             final LayoutInfo parentInfo, final TextLayoutInfo textLayoutInfo,
677             final LayoutContext now, final Graphics2D g2d) {
678         final float calcScaleY;
679         final float calcBaselineShift;
680         final float realDescent = textLayoutInfo.getDescent();
681         final float realAscent = textLayoutInfo.getAscent();
682         float targetNAscent;
683         float targetNDescent;
684         if (this.isFence()) {
685             targetNAscent = Math.max(parentInfo
686                     .getAscentHeight(LayoutStage.STAGE1), realAscent);
687             targetNDescent = Math.max(parentInfo
688                     .getDescentHeight(LayoutStage.STAGE1), realDescent);
689         } else {
690             targetNAscent = Math.max(parentInfo.getStretchAscent(), realAscent);
691             targetNDescent = Math.max(parentInfo.getStretchDescent(),
692                     realDescent);
693         }
694         if (this.isSymmetric()) {
695             final float middle = this.getMiddleShift(g2d, now);
696             final float ascentAboveMiddle = targetNAscent - middle;
697             final float descentBelowMiddle = targetNDescent + middle;
698             final float halfHeight = Math.max(ascentAboveMiddle,
699                     descentBelowMiddle);
700             targetNAscent = halfHeight + middle;
701             targetNDescent = halfHeight - middle;
702         }
703         final float targetNHeight = targetNAscent + targetNDescent;
704         final float realHeight = realAscent + realDescent;
705 
706         // TODO: MaxSize / MinSize could also be inherited from MStyle.
707         final float maxSize = AttributesHelper.parseRelativeSize(this
708                 .getMaxsize(), now, realHeight);
709         final float minSize = AttributesHelper.parseRelativeSize(this
710                 .getMinsize(), now, realHeight);
711         final float targetHeight = Math.max(Math.min(targetNHeight, maxSize),
712                 minSize);
713         final float targetDescent = targetHeight / targetNHeight
714                 * (targetNHeight / 2.0f)
715                 - (targetNHeight / 2.0f - targetNDescent);
716 
717         if (realHeight > 0.0f) {
718             calcScaleY = targetHeight / realHeight;
719         } else {
720             calcScaleY = 1.0f;
721         }
722         final float realDescentScaled = realDescent * calcScaleY;
723         calcBaselineShift = targetDescent - realDescentScaled;
724 
725         info.setDescentHeight(targetDescent, LayoutStage.STAGE2);
726         info.setAscentHeight(targetHeight - targetDescent, LayoutStage.STAGE2);
727         return new float[] { calcScaleY, calcBaselineShift };
728     }
729 
730     /** {@inheritDoc} */
731     public void handleEvent(final Event evt) {
732         this.changeHook();
733     }
734 }