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 }