Coverage Report - net.sourceforge.jeuclid.elements.presentation.token.Mo
 
Classes in this File Line Coverage Branch Coverage Complexity
Mo
90%
226/251
84%
88/104
2,133
 
 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  209
 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  42491
         super(qname, odoc);
 165  
 
 166  42491
         this.setDefaultMathAttribute(Mo.ATTR_FORM,
 167  
                 OperatorDictionary.FORM_INFIX);
 168  42491
         this.setDefaultMathAttribute(Mo.ATTR_FENCE, Constants.FALSE);
 169  42491
         this.setDefaultMathAttribute(Mo.ATTR_SEPARATOR, Constants.FALSE);
 170  42491
         this.setDefaultMathAttribute(Mo.ATTR_LSPACE,
 171  
                 AttributesHelper.THICKMATHSPACE);
 172  42491
         this.setDefaultMathAttribute(Mo.ATTR_RSPACE,
 173  
                 AttributesHelper.THICKMATHSPACE);
 174  42491
         this.setDefaultMathAttribute(Mo.ATTR_STRETCHY, Constants.FALSE);
 175  42491
         this.setDefaultMathAttribute(Mo.ATTR_SYMMETRIC, Constants.TRUE);
 176  42491
         this
 177  
                 .setDefaultMathAttribute(Mo.ATTR_MAXSIZE,
 178  
                         AttributesHelper.INFINITY);
 179  42491
         this.setDefaultMathAttribute(Mo.ATTR_MINSIZE, "1");
 180  42491
         this.setDefaultMathAttribute(Mo.ATTR_LARGEOP, Constants.FALSE);
 181  42491
         this.setDefaultMathAttribute(Mo.ATTR_MOVABLELIMITS, Constants.FALSE);
 182  42491
         this.setDefaultMathAttribute(Mo.ATTR_ACCENT, Constants.FALSE);
 183  42491
         this.opDict = OperatorDictionary2.getInstance();
 184  42491
     }
 185  
 
 186  
     /** {@inheritDoc} */
 187  
     @Override
 188  
     protected Node newNode() {
 189  0
         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  39919
         if (((Integer) now.getParameter(Parameter.SCRIPTLEVEL)) > 0) {
 199  4389
             return 0.0f;
 200  
         } else {
 201  35530
             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  5434
         if (Display.BLOCK.equals(now.getParameter(Parameter.DISPLAY))) {
 214  4598
             return Mo.LARGEOP_CORRECTOR_BLOCK;
 215  
         } else {
 216  836
             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  30932
         if (((Integer) now.getParameter(Parameter.SCRIPTLEVEL)) > 0) {
 227  3344
             return 0.0f;
 228  
         } else {
 229  27588
             return AttributesHelper.convertSizeToPt(this.getRspace(), now,
 230  
                     AttributesHelper.PT);
 231  
         }
 232  
     }
 233  
 
 234  
     private boolean isFence() {
 235  3124580
         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  0
         this.setAttribute(Mo.ATTR_MAXSIZE, maxsize);
 246  0
     }
 247  
 
 248  
     /**
 249  
      * Gets value of maxsize property.
 250  
      * 
 251  
      * @return Maxsize value.
 252  
      */
 253  
     public String getMaxsize() {
 254  7524
         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  0
         this.setAttribute(Mo.ATTR_MINSIZE, minsize);
 265  0
     }
 266  
 
 267  
     /**
 268  
      * Gets value of minsize property.
 269  
      * 
 270  
      * @return Minsize value.
 271  
      */
 272  
     public String getMinsize() {
 273  7524
         return this.getMathAttribute(Mo.ATTR_MINSIZE);
 274  
     }
 275  
 
 276  
     private TextLayout produceUnstrechtedLayout(final Graphics2D g,
 277  
             final LayoutContext now) {
 278  24453
         assert g != null : "Graphics2d is null in produceUnstrechtedLayout";
 279  24453
         float fontSizeInPoint = GraphicsSupport.getFontsizeInPoint(now);
 280  24453
         if (Boolean.parseBoolean(this.getLargeop())) {
 281  5434
             fontSizeInPoint *= this.getLargeOpCorrector(now);
 282  
         }
 283  
 
 284  24453
         final String theText = this.getText();
 285  24453
         final AttributedString aString = StringUtil
 286  
                 .convertStringtoAttributedString(theText, this
 287  
                         .getMathvariantAsVariant(), fontSizeInPoint, now);
 288  24453
         final TextLayout theLayout = StringUtil
 289  
                 .createTextLayoutFromAttributedString(g, aString, now);
 290  24453
         return theLayout;
 291  
     }
 292  
 
 293  
     /** {@inheritDoc} */
 294  
     @Override
 295  
     public void changeHook() {
 296  3117056
         super.changeHook();
 297  3117056
         if (!this.inChangeHook) {
 298  3117056
             this.inChangeHook = true;
 299  3117056
             this.detectFormParameter();
 300  3117056
             this.loadAttributeFromDictionary(Mo.ATTR_LARGEOP, Constants.FALSE);
 301  3117056
             this.loadAttributeFromDictionary(Mo.ATTR_SYMMETRIC, Constants.TRUE);
 302  3117056
             this.loadAttributeFromDictionary(Mo.ATTR_STRETCHY, Constants.FALSE);
 303  3117056
             this.loadAttributeFromDictionary(Mo.ATTR_FENCE, Constants.FALSE);
 304  3117056
             this.loadAttributeFromDictionary(Mo.ATTR_LSPACE,
 305  
                     AttributesHelper.THICKMATHSPACE);
 306  3117056
             this.loadAttributeFromDictionary(Mo.ATTR_RSPACE,
 307  
                     AttributesHelper.THICKMATHSPACE);
 308  3117056
             this.loadAttributeFromDictionary(Mo.ATTR_MOVABLELIMITS,
 309  
                     Constants.FALSE);
 310  
             // TODO: Load all.
 311  3117056
             this.registerWithParentsForEvents();
 312  3117056
             if (this.isFence()) {
 313  1941610
                 this.setDefaultMathAttribute(Mo.ATTR_STRETCHY,
 314  
                         Mo.VALUE_STRETCHY_VERTICAL);
 315  
             }
 316  3117056
             final CustomEvent evt = new DOMCustomEvent();
 317  3117056
             evt.initCustomEventNS(null, Mo.MOEVENT, true, false, null);
 318  3117056
             this.dispatchEvent(evt);
 319  3117056
             this.inChangeHook = false;
 320  
         }
 321  3117056
     }
 322  
 
 323  
     private void registerWithParentsForEvents() {
 324  3117056
         JEuclidElement parent = this.getParent();
 325  7440251
         while (parent != null) {
 326  4323195
             if (parent instanceof EventTarget) {
 327  4323195
                 ((EventTarget) parent).addEventListener("DOMSubtreeModified",
 328  
                         this, false);
 329  
             }
 330  4323195
             if ((parent instanceof Mrow) && (parent.getMathElementCount() > 1)) {
 331  2892381
                 parent = null;
 332  
             } else {
 333  1430814
                 parent = parent.getParent();
 334  
             }
 335  
         }
 336  3117056
     }
 337  
 
 338  
     private void loadAttributeFromDictionary(final String attrname,
 339  
             final String defvalue) {
 340  
         String attr;
 341  
         try {
 342  21819392
             attr = this.opDict.getDefaultAttributeValue(this.getText(), this
 343  
                     .getForm(), attrname);
 344  0
         } catch (final UnknownAttributeException e) {
 345  0
             attr = defvalue;
 346  21819392
         }
 347  21819392
         if (attr.equals(OperatorDictionary.VALUE_UNKNOWN)) {
 348  0
             attr = defvalue;
 349  
         }
 350  21819392
         this.setDefaultMathAttribute(attrname, attr);
 351  
 
 352  21819392
     }
 353  
 
 354  
     private void detectFormParameter() {
 355  
         final String form;
 356  3117056
         final JEuclidElement parent = this.getParent();
 357  3117056
         if ((parent != null) && (parent instanceof Mrow)) {
 358  2558399
             final int index = parent.getIndexOfMathElement(this);
 359  2558399
             if ((index == 0) && (parent.getMathElementCount() > 0)) {
 360  8360
                 form = OperatorDictionary.FORM_PREFIX;
 361  
             } else {
 362  2550039
                 if ((index == (parent.getMathElementCount() - 1))
 363  
                         && (parent.getMathElementCount() > 0)) {
 364  1343145
                     form = OperatorDictionary.FORM_POSTFIX;
 365  
                 } else {
 366  1206894
                     form = OperatorDictionary.FORM_INFIX;
 367  
                 }
 368  
             }
 369  2558399
         } else {
 370  558657
             form = OperatorDictionary.FORM_INFIX;
 371  
         }
 372  3117056
         this.setDefaultMathAttribute(Mo.ATTR_FORM, form);
 373  
         // TODO: Exception for embellished operators
 374  3117056
     }
 375  
 
 376  
     /** {@inheritDoc} */
 377  
     public String getLargeop() {
 378  24453
         return this.getMathAttribute(Mo.ATTR_LARGEOP);
 379  
     }
 380  
 
 381  
     /** {@inheritDoc} */
 382  
     public String getLspace() {
 383  35530
         return this.getMathAttribute(Mo.ATTR_LSPACE);
 384  
     }
 385  
 
 386  
     /** {@inheritDoc} */
 387  
     public String getMovablelimits() {
 388  7524
         final String wrongAttr = this.getMathAttribute(Mo.ATTR_MOVEABLEWRONG,
 389  
                 false);
 390  7524
         if (wrongAttr == null) {
 391  7524
             return this.getMathAttribute(Mo.ATTR_MOVABLELIMITS);
 392  
         } else {
 393  0
             return wrongAttr;
 394  
         }
 395  
     }
 396  
 
 397  
     /** {@inheritDoc} */
 398  
     public String getRspace() {
 399  27588
         return this.getMathAttribute(Mo.ATTR_RSPACE);
 400  
     }
 401  
 
 402  
     /** {@inheritDoc} */
 403  
     public void setAccent(final String accent) {
 404  0
         this.setAttribute(Mo.ATTR_ACCENT, accent);
 405  0
     }
 406  
 
 407  
     /** {@inheritDoc} */
 408  
     public void setFence(final String fence) {
 409  418
         this.setAttribute(Mo.ATTR_FENCE, fence);
 410  418
     }
 411  
 
 412  
     /** {@inheritDoc} */
 413  
     public void setForm(final String form) {
 414  418
         this.setAttribute(Mo.ATTR_FORM, form);
 415  418
     }
 416  
 
 417  
     /** {@inheritDoc} */
 418  
     public void setLargeop(final String largeop) {
 419  0
         this.setAttribute(Mo.ATTR_LARGEOP, largeop);
 420  0
     }
 421  
 
 422  
     /** {@inheritDoc} */
 423  
     public void setLspace(final String lspace) {
 424  418
         this.setAttribute(Mo.ATTR_LSPACE, lspace);
 425  418
     }
 426  
 
 427  
     /** {@inheritDoc} */
 428  
     public void setMovablelimits(final String movablelimits) {
 429  0
         this.setAttribute(Mo.ATTR_MOVABLELIMITS, movablelimits);
 430  0
     }
 431  
 
 432  
     /** {@inheritDoc} */
 433  
     public void setRspace(final String rspace) {
 434  418
         this.setAttribute(Mo.ATTR_RSPACE, rspace);
 435  418
     }
 436  
 
 437  
     /** {@inheritDoc} */
 438  
     public void setSeparator(final String separator) {
 439  0
         this.setAttribute(Mo.ATTR_SEPARATOR, separator);
 440  0
     }
 441  
 
 442  
     /** {@inheritDoc} */
 443  
     public void setStretchy(final String stretchy) {
 444  418
         this.setAttribute(Mo.ATTR_STRETCHY, stretchy);
 445  418
     }
 446  
 
 447  
     /** {@inheritDoc} */
 448  
     public void setSymmetric(final String symmetric) {
 449  418
         this.setAttribute(Mo.ATTR_SYMMETRIC, symmetric);
 450  418
     }
 451  
 
 452  
     /** {@inheritDoc} */
 453  
     public String getFence() {
 454  3124580
         return this.getMathAttribute(Mo.ATTR_FENCE);
 455  
     }
 456  
 
 457  
     /** {@inheritDoc} */
 458  
     public String getForm() {
 459  21819392
         return this.getMathAttribute(Mo.ATTR_FORM);
 460  
     }
 461  
 
 462  
     /** {@inheritDoc} */
 463  
     public String getSeparator() {
 464  0
         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  27588
         final Attr attr = this.getAttributeNodeNS(Constants.NS_JEUCLID_EXT,
 478  
                 Mo.ATTR_STRETCHY);
 479  27588
         if (attr == null) {
 480  26334
             retVal = this.getMathAttribute(Mo.ATTR_STRETCHY);
 481  
         } else {
 482  1254
             retVal = attr.getValue().trim();
 483  
         }
 484  27588
         return retVal;
 485  
     }
 486  
 
 487  
     /** {@inheritDoc} */
 488  
     public String getStretchy() {
 489  2299
         final String stretchVal = this.getExtendedStretchy();
 490  2299
         if ((Mo.VALUE_STRETCHY_HORIZONTAL.equalsIgnoreCase(stretchVal))
 491  
                 || (Mo.VALUE_STRETCHY_VERTICAL.equalsIgnoreCase(stretchVal))) {
 492  1045
             return Constants.TRUE;
 493  
         } else {
 494  1254
             return stretchVal;
 495  
         }
 496  
     }
 497  
 
 498  
     private boolean isStretchyHorizontal(final String stretchValue) {
 499  24453
         return Mo.VALUE_STRETCHY_HORIZONTAL.equalsIgnoreCase(stretchValue)
 500  
                 || Boolean.parseBoolean(stretchValue);
 501  
     }
 502  
 
 503  
     private boolean isStretchyVertical(final String stretchValue) {
 504  22363
         return Mo.VALUE_STRETCHY_VERTICAL.equalsIgnoreCase(stretchValue)
 505  
                 || Boolean.parseBoolean(stretchValue);
 506  
     }
 507  
 
 508  
     private boolean isStretchy() {
 509  15466
         final String stretchValue = this.getExtendedStretchy();
 510  15466
         return this.isStretchyHorizontal(stretchValue)
 511  
                 || this.isStretchyVertical(stretchValue);
 512  
     }
 513  
 
 514  
     /** {@inheritDoc} */
 515  
     public String getAccent() {
 516  0
         return this.getMathAttribute(Mo.ATTR_ACCENT);
 517  
     }
 518  
 
 519  
     /** {@inheritDoc} */
 520  
     public String getSymmetric() {
 521  7524
         return this.getMathAttribute(Mo.ATTR_SYMMETRIC);
 522  
     }
 523  
 
 524  
     private boolean isSymmetric() {
 525  7524
         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  15466
         final LayoutContext now = this.applyLocalAttributesToContext(context);
 533  15466
         final Graphics2D g = view.getGraphics();
 534  15466
         final TextLayout t = this.produceUnstrechtedLayout(g, now);
 535  
 
 536  15466
         final StringUtil.TextLayoutInfo tli = StringUtil.getTextLayoutInfo(t,
 537  
                 true);
 538  15466
         final float ascent = tli.getAscent();
 539  15466
         final float descent = tli.getDescent();
 540  15466
         final float xOffset = tli.getOffset();
 541  15466
         final float contentWidth = tli.getWidth() + xOffset;
 542  15466
         final JEuclidElement parent = this.getParent();
 543  15466
         float lspace = this.getLspaceAsFloat(now);
 544  15466
         float rspace = this.getRspaceAsFloat(now);
 545  15466
         if ((parent != null) && (parent.hasChildPostscripts(this, context))) {
 546  0
             rspace = 0.0f;
 547  
         } else {
 548  15466
             rspace = this.getRspaceAsFloat(now);
 549  
         }
 550  15466
         if ((parent != null) && (parent.hasChildPrescripts(this))) {
 551  0
             lspace = 0.0f;
 552  
         } else {
 553  15466
             lspace = this.getLspaceAsFloat(now);
 554  
         }
 555  
 
 556  15466
         info.setAscentHeight(ascent, LayoutStage.STAGE1);
 557  15466
         info.setDescentHeight(descent, LayoutStage.STAGE1);
 558  15466
         info.setHorizontalCenterOffset(lspace + contentWidth / 2.0f,
 559  
                 LayoutStage.STAGE1);
 560  15466
         info.setWidth(lspace + contentWidth + rspace, LayoutStage.STAGE1);
 561  15466
         if (this.isStretchy()) {
 562  8987
             info.setLayoutStage(LayoutStage.STAGE1);
 563  8987
             info.setStretchAscent(0.0f);
 564  8987
             info.setStretchDescent(0.0f);
 565  
         } else {
 566  6479
             info.setGraphicsObject(new TextObject(t, lspace + tli.getOffset(),
 567  
                     0, null, (Color) now.getParameter(Parameter.MATHCOLOR)));
 568  6479
             info.setLayoutStage(LayoutStage.STAGE2);
 569  
         }
 570  15466
     }
 571  
 
 572  
     /** {@inheritDoc} */
 573  
     @Override
 574  
     public void layoutStage2(final LayoutView view, final LayoutInfo info,
 575  
             final LayoutContext context) {
 576  8987
         final LayoutContext now = this.applyLocalAttributesToContext(context);
 577  
 
 578  8987
         final Graphics2D g = view.getGraphics();
 579  8987
         final TextLayout t = this.produceUnstrechtedLayout(g, now);
 580  
 
 581  
         final float calcScaleY;
 582  
         final float calcScaleX;
 583  
         final float calcBaselineShift;
 584  8987
         final String stretchValue = this.getExtendedStretchy();
 585  8987
         boolean stretchVertically = this.isStretchyVertical(stretchValue);
 586  8987
         final boolean stretchHorizontally = this
 587  
                 .isStretchyHorizontal(stretchValue);
 588  
 
 589  8987
         JEuclidElement horizParent = null;
 590  8987
         JEuclidElement parent = this;
 591  
         JEuclidElement last;
 592  
         boolean cont;
 593  
         do {
 594  13585
             last = parent;
 595  13585
             parent = parent.getParent();
 596  13585
             cont = false;
 597  13585
             if ((parent instanceof Mrow) && (parent.getMathElementCount() == 1)) {
 598  
                 // Ignore single element Mrows.
 599  627
                 cont = true;
 600  12958
             } else if ((parent instanceof MathMLUnderOverElement)
 601  
                     || (parent instanceof MathMLScriptElement)) {
 602  
                 // Special Treatment for UnderOverElements to match stretchVert1
 603  
                 // test.
 604  
                 final boolean isBase;
 605  3971
                 if (parent instanceof MathMLUnderOverElement) {
 606  3971
                     final MathMLUnderOverElement munderover = (MathMLUnderOverElement) parent;
 607  3971
                     isBase = munderover.getBase() == last;
 608  3971
                 } else {
 609  0
                     final MathMLScriptElement munderover = (MathMLScriptElement) parent;
 610  0
                     isBase = munderover.getBase() == last;
 611  
                 }
 612  3971
                 if (!isBase) {
 613  1045
                     stretchVertically = false;
 614  
                 }
 615  3971
                 horizParent = parent;
 616  3971
                 cont = true;
 617  
             }
 618  13585
         } while (cont);
 619  8987
         if (horizParent == null) {
 620  6061
             horizParent = parent;
 621  
         }
 622  
 
 623  8987
         final LayoutInfo parentInfo = view.getInfo(parent);
 624  8987
         final TextLayoutInfo textLayoutInfo = StringUtil.getTextLayoutInfo(t,
 625  
                 true);
 626  8987
         if (parentInfo == null) {
 627  418
             calcScaleX = 1.0f;
 628  418
             calcScaleY = 1.0f;
 629  418
             calcBaselineShift = 0.0f;
 630  
         } else {
 631  8569
             if (stretchVertically) {
 632  7524
                 final float[] yf = this.calcYScaleFactorAndBaselineShift(info,
 633  
                         parentInfo, textLayoutInfo, now, g);
 634  7524
                 calcScaleY = yf[0];
 635  7524
                 calcBaselineShift = yf[1];
 636  7524
             } else {
 637  1045
                 calcScaleY = 1.0f;
 638  1045
                 calcBaselineShift = 0.0f;
 639  
             }
 640  8569
             if (stretchHorizontally) {
 641  1672
                 calcScaleX = this.calcXScaleFactor(info, view
 642  
                         .getInfo(horizParent), textLayoutInfo);
 643  
             } else {
 644  6897
                 calcScaleX = 1.0f;
 645  
             }
 646  
         }
 647  8987
         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  8987
         info.setLayoutStage(LayoutStage.STAGE2);
 652  8987
     }
 653  
 
 654  
     private float calcXScaleFactor(final LayoutInfo info,
 655  
             final LayoutInfo parentInfo, final TextLayoutInfo textLayoutInfo) {
 656  
         final float calcScaleX;
 657  1672
         final float rstretchWidth = parentInfo.getStretchWidth();
 658  1672
         if (rstretchWidth > 0.0f) {
 659  1045
             final float realwidth = textLayoutInfo.getWidth();
 660  1045
             if (realwidth > 0) {
 661  1045
                 final float stretchWidth = Math.max(realwidth, rstretchWidth);
 662  1045
                 calcScaleX = stretchWidth / realwidth;
 663  1045
                 info.setWidth(stretchWidth, LayoutStage.STAGE2);
 664  1045
                 info.setHorizontalCenterOffset(stretchWidth / 2.0f,
 665  
                         LayoutStage.STAGE2);
 666  1045
             } else {
 667  0
                 calcScaleX = 1.0f;
 668  
             }
 669  1045
         } else {
 670  627
             calcScaleX = 1.0f;
 671  
         }
 672  1672
         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  7524
         final float realDescent = textLayoutInfo.getDescent();
 681  7524
         final float realAscent = textLayoutInfo.getAscent();
 682  
         float targetNAscent;
 683  
         float targetNDescent;
 684  7524
         if (this.isFence()) {
 685  5016
             targetNAscent = Math.max(parentInfo
 686  
                     .getAscentHeight(LayoutStage.STAGE1), realAscent);
 687  5016
             targetNDescent = Math.max(parentInfo
 688  
                     .getDescentHeight(LayoutStage.STAGE1), realDescent);
 689  
         } else {
 690  2508
             targetNAscent = Math.max(parentInfo.getStretchAscent(), realAscent);
 691  2508
             targetNDescent = Math.max(parentInfo.getStretchDescent(),
 692  
                     realDescent);
 693  
         }
 694  7524
         if (this.isSymmetric()) {
 695  7106
             final float middle = this.getMiddleShift(g2d, now);
 696  7106
             final float ascentAboveMiddle = targetNAscent - middle;
 697  7106
             final float descentBelowMiddle = targetNDescent + middle;
 698  7106
             final float halfHeight = Math.max(ascentAboveMiddle,
 699  
                     descentBelowMiddle);
 700  7106
             targetNAscent = halfHeight + middle;
 701  7106
             targetNDescent = halfHeight - middle;
 702  
         }
 703  7524
         final float targetNHeight = targetNAscent + targetNDescent;
 704  7524
         final float realHeight = realAscent + realDescent;
 705  
 
 706  
         // TODO: MaxSize / MinSize could also be inherited from MStyle.
 707  7524
         final float maxSize = AttributesHelper.parseRelativeSize(this
 708  
                 .getMaxsize(), now, realHeight);
 709  7524
         final float minSize = AttributesHelper.parseRelativeSize(this
 710  
                 .getMinsize(), now, realHeight);
 711  7524
         final float targetHeight = Math.max(Math.min(targetNHeight, maxSize),
 712  
                 minSize);
 713  7524
         final float targetDescent = targetHeight / targetNHeight
 714  
                 * (targetNHeight / 2.0f)
 715  
                 - (targetNHeight / 2.0f - targetNDescent);
 716  
 
 717  7524
         if (realHeight > 0.0f) {
 718  7524
             calcScaleY = targetHeight / realHeight;
 719  
         } else {
 720  0
             calcScaleY = 1.0f;
 721  
         }
 722  7524
         final float realDescentScaled = realDescent * calcScaleY;
 723  7524
         calcBaselineShift = targetDescent - realDescentScaled;
 724  
 
 725  7524
         info.setDescentHeight(targetDescent, LayoutStage.STAGE2);
 726  7524
         info.setAscentHeight(targetHeight - targetDescent, LayoutStage.STAGE2);
 727  7524
         return new float[] { calcScaleY, calcBaselineShift };
 728  
     }
 729  
 
 730  
     /** {@inheritDoc} */
 731  
     public void handleEvent(final Event evt) {
 732  2913089
         this.changeHook();
 733  2913089
     }
 734  
 }