001    /*
002     * Copyright 2002 - 2007 JEuclid, http://jeuclid.sf.net
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    /* $Id: Mo.java,v e2b3e25686bf 2009/09/29 19:14:25 max $ */
018    
019    package net.sourceforge.jeuclid.elements.presentation.token;
020    
021    import java.awt.Color;
022    import java.awt.Graphics2D;
023    import java.awt.font.TextLayout;
024    import java.awt.geom.AffineTransform;
025    import java.text.AttributedString;
026    
027    import net.sourceforge.jeuclid.Constants;
028    import net.sourceforge.jeuclid.LayoutContext;
029    import net.sourceforge.jeuclid.context.Display;
030    import net.sourceforge.jeuclid.context.Parameter;
031    import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
032    import net.sourceforge.jeuclid.elements.JEuclidElement;
033    import net.sourceforge.jeuclid.elements.presentation.general.Mrow;
034    import net.sourceforge.jeuclid.elements.support.GraphicsSupport;
035    import net.sourceforge.jeuclid.elements.support.attributes.AttributesHelper;
036    import net.sourceforge.jeuclid.elements.support.operatordict.OperatorDictionary;
037    import net.sourceforge.jeuclid.elements.support.operatordict.OperatorDictionary2;
038    import net.sourceforge.jeuclid.elements.support.operatordict.UnknownAttributeException;
039    import net.sourceforge.jeuclid.elements.support.text.StringUtil;
040    import net.sourceforge.jeuclid.elements.support.text.StringUtil.TextLayoutInfo;
041    import net.sourceforge.jeuclid.layout.LayoutInfo;
042    import net.sourceforge.jeuclid.layout.LayoutStage;
043    import net.sourceforge.jeuclid.layout.LayoutView;
044    import net.sourceforge.jeuclid.layout.TextObject;
045    
046    import org.apache.batik.dom.AbstractDocument;
047    import org.apache.batik.dom.events.DOMCustomEvent;
048    import org.w3c.dom.Attr;
049    import org.w3c.dom.Node;
050    import org.w3c.dom.events.CustomEvent;
051    import org.w3c.dom.events.Event;
052    import org.w3c.dom.events.EventListener;
053    import org.w3c.dom.events.EventTarget;
054    import org.w3c.dom.mathml.MathMLOperatorElement;
055    import org.w3c.dom.mathml.MathMLScriptElement;
056    import org.w3c.dom.mathml.MathMLUnderOverElement;
057    
058    /**
059     * This class presents a math operator, like "(" or "*".
060     * 
061     * @version $Revision: e2b3e25686bf $
062     */
063    
064    // CHECKSTYLE:OFF
065    // Class Fan-out is to high. However, this is required due to complexity of
066    // mo.
067    public final class Mo extends AbstractJEuclidElement implements
068            MathMLOperatorElement, EventListener {
069        // CHECKSTYLE:ON
070    
071        /** Attribute for form. */
072        public static final String ATTR_FORM = "form";
073    
074        /** Attribute for separator. */
075        public static final String ATTR_SEPARATOR = "separator";
076    
077        /** Attribute for lspace. */
078        public static final String ATTR_LSPACE = "lspace";
079    
080        /** Attribute for rspace. */
081        public static final String ATTR_RSPACE = "rspace";
082    
083        /** Attribute for min size. */
084        public static final String ATTR_MINSIZE = "minsize";
085    
086        /** Attribute for max size. */
087        public static final String ATTR_MAXSIZE = "maxsize";
088    
089        /** Wrong attribute name for movable limits. */
090        public static final String ATTR_MOVEABLEWRONG = "moveablelimits";
091    
092        /** Attribute for movable limits. */
093        public static final String ATTR_MOVABLELIMITS = "movablelimits";
094    
095        /** Attribute for accent. */
096        public static final String ATTR_ACCENT = "accent";
097    
098        /**
099         * 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    }