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 }