View Javadoc

1   /*
2    * Copyright 2007 - 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: AbstractUnderOver.java,v 2986a8eeaebc 2009/09/24 12:53:08 max $ */
18  
19  package net.sourceforge.jeuclid.elements.presentation.script;
20  
21  import java.awt.geom.Dimension2D;
22  
23  import net.sourceforge.jeuclid.LayoutContext;
24  import net.sourceforge.jeuclid.context.Display;
25  import net.sourceforge.jeuclid.context.InlineLayoutContext;
26  import net.sourceforge.jeuclid.context.Parameter;
27  import net.sourceforge.jeuclid.context.RelativeScriptlevelLayoutContext;
28  import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
29  import net.sourceforge.jeuclid.elements.JEuclidElement;
30  import net.sourceforge.jeuclid.elements.support.Dimension2DImpl;
31  import net.sourceforge.jeuclid.elements.support.ElementListSupport;
32  import net.sourceforge.jeuclid.elements.support.attributes.AttributesHelper;
33  import net.sourceforge.jeuclid.layout.LayoutInfo;
34  import net.sourceforge.jeuclid.layout.LayoutStage;
35  import net.sourceforge.jeuclid.layout.LayoutView;
36  
37  import org.apache.batik.dom.AbstractDocument;
38  import org.w3c.dom.mathml.MathMLElement;
39  import org.w3c.dom.mathml.MathMLOperatorElement;
40  import org.w3c.dom.mathml.MathMLUnderOverElement;
41  
42  /**
43   * Implementation and helper methods for munder, mover, and munderover.
44   * <p>
45   * TODO: some operators should "default" to being an accent, but currently they
46   * don't
47   * 
48   * @version $Revision: 2986a8eeaebc $
49   */
50  public abstract class AbstractUnderOver extends AbstractJEuclidElement
51          implements MathMLUnderOverElement {
52  
53      /**
54       * Space between base and under/over for accents.
55       */
56      public static final String UNDER_OVER_SPACE = "0.1ex";
57  
58      /** Space for non-accents multiplied by this value. */
59      public static final float NON_ACCENT_MULTIPLIER = 2.5f;
60  
61      /** attribute for accent property. */
62      public static final String ATTR_ACCENT = "accent";
63  
64      /** attribute for accentunder property. */
65      public static final String ATTR_ACCENTUNDER = "accentunder";
66  
67      /** No attributes, so just use 1. */
68      private static final long serialVersionUID = 1L;
69  
70      /**
71       * Default constructor. Sets MathML Namespace.
72       * 
73       * @param qname
74       *            Qualified name.
75       * @param odoc
76       *            Owner Document.
77       */
78      public AbstractUnderOver(final String qname, final AbstractDocument odoc) {
79          super(qname, odoc);
80      }
81  
82      /** {@inheritDoc} */
83      public String getAccent() {
84          // TODO: Accent also depends on the content. See spec 3.4.4 - 3.4.6
85          return this.getMathAttribute(AbstractUnderOver.ATTR_ACCENT);
86      }
87  
88      /**
89       * returns the accent property as boolean.
90       * 
91       * @return accent
92       */
93      protected boolean getAccentAsBoolean() {
94          return Boolean.parseBoolean(this.getAccent());
95      }
96  
97      /**
98       * @param context
99       *            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 }