001 /*
002 * Copyright 2007 - 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: AbstractUnderOver.java,v 2986a8eeaebc 2009/09/24 12:53:08 max $ */
018
019 package net.sourceforge.jeuclid.elements.presentation.script;
020
021 import java.awt.geom.Dimension2D;
022
023 import net.sourceforge.jeuclid.LayoutContext;
024 import net.sourceforge.jeuclid.context.Display;
025 import net.sourceforge.jeuclid.context.InlineLayoutContext;
026 import net.sourceforge.jeuclid.context.Parameter;
027 import net.sourceforge.jeuclid.context.RelativeScriptlevelLayoutContext;
028 import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
029 import net.sourceforge.jeuclid.elements.JEuclidElement;
030 import net.sourceforge.jeuclid.elements.support.Dimension2DImpl;
031 import net.sourceforge.jeuclid.elements.support.ElementListSupport;
032 import net.sourceforge.jeuclid.elements.support.attributes.AttributesHelper;
033 import net.sourceforge.jeuclid.layout.LayoutInfo;
034 import net.sourceforge.jeuclid.layout.LayoutStage;
035 import net.sourceforge.jeuclid.layout.LayoutView;
036
037 import org.apache.batik.dom.AbstractDocument;
038 import org.w3c.dom.mathml.MathMLElement;
039 import org.w3c.dom.mathml.MathMLOperatorElement;
040 import org.w3c.dom.mathml.MathMLUnderOverElement;
041
042 /**
043 * Implementation and helper methods for munder, mover, and munderover.
044 * <p>
045 * TODO: some operators should "default" to being an accent, but currently they
046 * don't
047 *
048 * @version $Revision: 2986a8eeaebc $
049 */
050 public abstract class AbstractUnderOver extends AbstractJEuclidElement
051 implements MathMLUnderOverElement {
052
053 /**
054 * Space between base and under/over for accents.
055 */
056 public static final String UNDER_OVER_SPACE = "0.1ex";
057
058 /** Space for non-accents multiplied by this value. */
059 public static final float NON_ACCENT_MULTIPLIER = 2.5f;
060
061 /** attribute for accent property. */
062 public static final String ATTR_ACCENT = "accent";
063
064 /** attribute for accentunder property. */
065 public static final String ATTR_ACCENTUNDER = "accentunder";
066
067 /** No attributes, so just use 1. */
068 private static final long serialVersionUID = 1L;
069
070 /**
071 * Default constructor. Sets MathML Namespace.
072 *
073 * @param qname
074 * Qualified name.
075 * @param odoc
076 * Owner Document.
077 */
078 public AbstractUnderOver(final String qname, final AbstractDocument odoc) {
079 super(qname, odoc);
080 }
081
082 /** {@inheritDoc} */
083 public String getAccent() {
084 // TODO: Accent also depends on the content. See spec 3.4.4 - 3.4.6
085 return this.getMathAttribute(AbstractUnderOver.ATTR_ACCENT);
086 }
087
088 /**
089 * returns the accent property as boolean.
090 *
091 * @return accent
092 */
093 protected boolean getAccentAsBoolean() {
094 return Boolean.parseBoolean(this.getAccent());
095 }
096
097 /**
098 * @param context
099 * LayoutContext
100 * @return true if limits are moved (behave like under/over).
101 */
102 private boolean limitsAreMoved(final LayoutContext now) {
103 return (!this.getAccentAsBoolean())
104 && (this.getBase() instanceof MathMLOperatorElement)
105 && Boolean
106 .parseBoolean(((MathMLOperatorElement) this.getBase())
107 .getMovablelimits())
108 && (Display.INLINE.equals(now.getParameter(Parameter.DISPLAY)));
109 }
110
111 /** {@inheritDoc} */
112 public String getAccentunder() {
113 // TODO: Accent also depends on the content. See spec 3.4.4 - 3.4.6
114 return this.getMathAttribute(AbstractUnderOver.ATTR_ACCENTUNDER);
115 }
116
117 /** {@inheritDoc} */
118 @Override
119 public LayoutContext getChildLayoutContext(final int childNum,
120 final LayoutContext context) {
121 final LayoutContext now = this.applyLocalAttributesToContext(context);
122 if (childNum == 0) {
123 return now;
124 } else {
125 // TODO: Should depend on type and accent
126 return new RelativeScriptlevelLayoutContext(
127 new InlineLayoutContext(now), 1);
128 }
129 }
130
131 /**
132 * returns the accentunder property as boolean.
133 *
134 * @return accentunder
135 */
136 protected boolean getAccentunderAsBoolean() {
137 return Boolean.parseBoolean(this.getAccentunder());
138 }
139
140 /** {@inheritDoc} */
141 public JEuclidElement getBase() {
142 return this.getMathElement(0);
143 }
144
145 /** {@inheritDoc} */
146 public abstract JEuclidElement getOverscript();
147
148 /** {@inheritDoc} */
149 public abstract JEuclidElement getUnderscript();
150
151 /** {@inheritDoc} */
152 public void setAccent(final String accent) {
153 this.setAttribute(AbstractUnderOver.ATTR_ACCENT, accent);
154 }
155
156 /** {@inheritDoc} */
157 public void setAccentunder(final String accentunder) {
158 this.setAttribute(AbstractUnderOver.ATTR_ACCENTUNDER, accentunder);
159 }
160
161 /** {@inheritDoc} */
162 public void setBase(final MathMLElement base) {
163 this.setMathElement(0, base);
164 }
165
166 /** {@inheritDoc} */
167 @Override
168 public boolean hasChildPostscripts(final JEuclidElement child,
169 final LayoutContext context) {
170 return this.limitsAreMoved(context) && child.isSameNode(this.getBase());
171 }
172
173 /** {@inheritDoc} */
174 @Override
175 protected void layoutStageInvariant(final LayoutView view,
176 final LayoutInfo info, final LayoutStage stage,
177 final LayoutContext context) {
178 final LayoutContext now = this.applyLocalAttributesToContext(context);
179 if (this.limitsAreMoved(now)) {
180 ScriptSupport.layout(view, info, stage, now, this, this.getBase(),
181 this.getUnderscript(), this.getOverscript(), null, null);
182 } else {
183 this.layoutUnderOver(view, info, stage, now);
184 }
185 }
186
187 private void layoutUnderOver(final LayoutView view, final LayoutInfo info,
188 final LayoutStage stage, final LayoutContext now) {
189
190 final JEuclidElement base = this.getBase();
191 final JEuclidElement under = this.getUnderscript();
192 final JEuclidElement over = this.getOverscript();
193
194 final LayoutInfo baseInfo = view.getInfo(base);
195 final LayoutInfo underInfo;
196 final LayoutInfo overInfo;
197
198 float width = baseInfo.getWidth(stage);
199
200 final float extraShift = AttributesHelper.convertSizeToPt(
201 AbstractUnderOver.UNDER_OVER_SPACE, now, AttributesHelper.PT);
202
203 if (under == null) {
204 underInfo = null;
205 } else {
206 underInfo = view.getInfo(under);
207 width = Math.max(width, underInfo.getWidth(stage));
208 }
209 if (over == null) {
210 overInfo = null;
211 } else {
212 overInfo = view.getInfo(over);
213 width = Math.max(width, overInfo.getWidth(stage));
214 }
215 final float middle = width / 2.0f;
216
217 baseInfo.moveTo(middle - baseInfo.getHorizontalCenterOffset(stage), 0,
218 stage);
219
220 if (under != null) {
221 this.positionUnder(stage, baseInfo, underInfo, extraShift, middle);
222 }
223 if (over != null) {
224 this.positionOver(stage, baseInfo, overInfo, extraShift, middle);
225 }
226
227 final Dimension2D borderLeftTop = new Dimension2DImpl(0.0f, 0.0f);
228 final Dimension2D borderRightBottom = new Dimension2DImpl(0.0f, 0.0f);
229 ElementListSupport.fillInfoFromChildren(view, info, this, stage,
230 borderLeftTop, borderRightBottom);
231 info.setStretchWidth(width);
232 info.setStretchAscent(baseInfo.getStretchAscent());
233 info.setStretchDescent(baseInfo.getStretchDescent());
234 }
235
236 private void positionUnder(final LayoutStage stage,
237 final LayoutInfo baseInfo, final LayoutInfo underInfo,
238 final float extraShift, final float middle) {
239 final float underextra;
240 if (this.getAccentunderAsBoolean()) {
241 underextra = extraShift;
242 } else {
243 underextra = extraShift * AbstractUnderOver.NON_ACCENT_MULTIPLIER;
244 }
245 final float y = baseInfo.getDescentHeight(stage)
246 + underInfo.getAscentHeight(stage) + underextra;
247 underInfo.moveTo(middle - underInfo.getHorizontalCenterOffset(stage),
248 y, stage);
249 }
250
251 private void positionOver(final LayoutStage stage,
252 final LayoutInfo baseInfo, final LayoutInfo overInfo,
253 final float extraShift, final float middle) {
254 final float overextra;
255 if (this.getAccentAsBoolean()) {
256 overextra = extraShift;
257 } else {
258 overextra = extraShift * AbstractUnderOver.NON_ACCENT_MULTIPLIER;
259 }
260 final float y = baseInfo.getAscentHeight(stage)
261 + overInfo.getDescentHeight(stage) + overextra;
262 overInfo.moveTo(middle - overInfo.getHorizontalCenterOffset(stage), -y,
263 stage);
264 }
265 }