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    }