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 }