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: Menclose.java,v 353eda94b8dd 2009/10/14 10:04:09 max $ */
018    
019    package net.sourceforge.jeuclid.elements.presentation.general;
020    
021    import java.awt.Color;
022    import java.awt.geom.Dimension2D;
023    import java.lang.reflect.Constructor;
024    import java.lang.reflect.InvocationTargetException;
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.List;
028    import java.util.Locale;
029    import java.util.Map;
030    import java.util.Stack;
031    
032    import net.sourceforge.jeuclid.LayoutContext;
033    import net.sourceforge.jeuclid.context.Parameter;
034    import net.sourceforge.jeuclid.elements.AbstractElementWithDelegates;
035    import net.sourceforge.jeuclid.elements.JEuclidElement;
036    import net.sourceforge.jeuclid.elements.presentation.AbstractContainer;
037    import net.sourceforge.jeuclid.elements.support.Dimension2DImpl;
038    import net.sourceforge.jeuclid.elements.support.ElementListSupport;
039    import net.sourceforge.jeuclid.elements.support.GraphicsSupport;
040    import net.sourceforge.jeuclid.elements.support.attributes.AttributesHelper;
041    import net.sourceforge.jeuclid.layout.GraphicsObject;
042    import net.sourceforge.jeuclid.layout.LayoutInfo;
043    import net.sourceforge.jeuclid.layout.LayoutStage;
044    import net.sourceforge.jeuclid.layout.LayoutView;
045    import net.sourceforge.jeuclid.layout.LayoutableNode;
046    import net.sourceforge.jeuclid.layout.LineObject;
047    
048    import org.apache.batik.dom.AbstractDocument;
049    import org.apache.commons.logging.Log;
050    import org.apache.commons.logging.LogFactory;
051    import org.w3c.dom.Node;
052    import org.w3c.dom.mathml.MathMLEncloseElement;
053    
054    /**
055     * Class for supporting "menclose" elements.
056     * 
057     * @version $Revision: 353eda94b8dd $
058     */
059    public final class Menclose extends AbstractElementWithDelegates implements
060            MathMLEncloseElement {
061    
062        /**
063         * The XML element from this class.
064         */
065        public static final String ELEMENT = "menclose";
066    
067        /** The notation attribute. */
068        public static final String ATTR_NOTATION = "notation";
069    
070        private static final String LONGDIV = "longdiv";
071    
072        /**
073         * base class for all row-like notations.
074         * 
075         */
076        private abstract static class AbstractRowLikeNotation extends
077                AbstractContainer {
078    
079            /**
080             * No data, so SUID is does not matter.
081             */
082            private static final long serialVersionUID = 1L;
083    
084            /**
085             * Default constructor. Sets MathML Namespace.
086             * 
087             * @param qname
088             *            Qualified name.
089             * @param odoc
090             *            Owner Document.
091             */
092            public AbstractRowLikeNotation(final String qname,
093                    final AbstractDocument odoc) {
094                super(qname, odoc);
095            }
096    
097            /** {@inheritDoc} */
098            @Override
099            protected void layoutStageInvariant(final LayoutView view,
100                    final LayoutInfo info, final LayoutStage stage,
101                    final LayoutContext context) {
102                final LayoutContext now = this
103                        .applyLocalAttributesToContext(context);
104                final Dimension2D borderLeftTop = this.getBorderLeftTop(now);
105                final float borderLeft = (float) borderLeftTop.getWidth();
106                if (borderLeft > 0.0f) {
107                    view.getInfo((LayoutableNode) this.getFirstChild()).moveTo(
108                            borderLeft, 0, stage);
109                }
110                ElementListSupport.fillInfoFromChildren(view, info, this, stage,
111                        borderLeftTop, this.getBorderRightBottom(now));
112                this.enclHook(info, stage, this.applyLocalAttributesToContext(now));
113            }
114    
115            /**
116             * Add left / top border to enclosed object.
117             * 
118             * @param now
119             *            current Layout Context
120             * @return Left and Top border
121             */
122            protected Dimension2D getBorderLeftTop(final LayoutContext now) {
123                return new Dimension2DImpl(0.0f, 0.0f);
124            }
125    
126            /**
127             * Add right / bottom border to enclosed object.
128             * 
129             * @param now
130             *            current Layout Context
131             * @return Right and Bottom border
132             */
133            protected Dimension2D getBorderRightBottom(final LayoutContext now) {
134                return new Dimension2DImpl(0.0f, 0.0f);
135            }
136    
137            /**
138             * Add Graphic objects to enclosed object.
139             * 
140             * @param info
141             *            Layout Info
142             * @param stage
143             *            current Layout Stage
144             * @param now
145             *            current Layout Context
146             */
147            protected abstract void enclHook(final LayoutInfo info,
148                    final LayoutStage stage, final LayoutContext now);
149        }
150    
151        /**
152         * Represents the US long-division notation, to support the notation
153         * "longdiv".
154         * 
155         */
156        private static final class Longdiv extends Menclose.AbstractRowLikeNotation {
157            private static final long serialVersionUID = 1L;
158    
159            /**
160             * Default constructor. Sets MathML Namespace.
161             * 
162             * @param qname
163             *            Qualified name.
164             * @param odoc
165             *            Owner Document.
166             */
167            public Longdiv(final String qname, final AbstractDocument odoc) {
168                super(qname, odoc);
169            }
170    
171            @Override
172            protected Node newNode() {
173                return new Longdiv(this.nodeName, this.ownerDocument);
174            }
175    
176            /** {@inheritDoc} */
177            @Override
178            protected Dimension2D getBorderLeftTop(final LayoutContext now) {
179                final float lineSpace = GraphicsSupport.lineWidth(now) * 2;
180                return new Dimension2DImpl(AttributesHelper.convertSizeToPt(
181                        "0.25em", now, null)
182                        + lineSpace, lineSpace);
183            }
184    
185            /** {@inheritDoc} */
186            @Override
187            protected void enclHook(final LayoutInfo info, final LayoutStage stage,
188                    final LayoutContext now) {
189                final List<GraphicsObject> graphicObjects = info
190                        .getGraphicObjects();
191                graphicObjects.clear();
192                final float lineWidth = GraphicsSupport.lineWidth(now);
193                final float top = info.getAscentHeight(stage) + lineWidth;
194                final Color color = (Color) now.getParameter(Parameter.MATHCOLOR);
195                graphicObjects.add(new LineObject(lineWidth, -top, lineWidth, info
196                        .getDescentHeight(stage), lineWidth, color));
197                graphicObjects.add(new LineObject(lineWidth, -top, info
198                        .getWidth(stage), -top, lineWidth, color));
199            }
200        }
201    
202        /**
203         * Up-Diagonal Strike.
204         * 
205         */
206        private static final class Updiagonalstrike extends
207                Menclose.AbstractRowLikeNotation {
208    
209            private static final long serialVersionUID = 1L;
210    
211            /**
212             * Default constructor. Sets MathML Namespace.
213             * 
214             * @param qname
215             *            Qualified name.
216             * @param odoc
217             *            Owner Document.
218             */
219            public Updiagonalstrike(final String qname, final AbstractDocument odoc) {
220                super(qname, odoc);
221            }
222    
223            @Override
224            protected Node newNode() {
225                return new Updiagonalstrike(this.nodeName, this.ownerDocument);
226            }
227    
228            /** {@inheritDoc} */
229            @Override
230            protected void enclHook(final LayoutInfo info, final LayoutStage stage,
231                    final LayoutContext now) {
232    
233                final Color color = (Color) now.getParameter(Parameter.MATHCOLOR);
234                final float lineWidth = GraphicsSupport.lineWidth(now);
235                info.setGraphicsObject(new LineObject(0, info
236                        .getDescentHeight(stage), info.getWidth(stage), -info
237                        .getAscentHeight(stage), lineWidth, color));
238            }
239    
240        }
241    
242        /**
243         * Down-Diagonal Strike.
244         * 
245         */
246        private static final class Downdiagonalstrike extends
247                Menclose.AbstractRowLikeNotation {
248    
249            private static final long serialVersionUID = 1L;
250    
251            /**
252             * Default constructor. Sets MathML Namespace.
253             * 
254             * @param qname
255             *            Qualified name.
256             * @param odoc
257             *            Owner Document.
258             */
259            public Downdiagonalstrike(final String qname,
260                    final AbstractDocument odoc) {
261                super(qname, odoc);
262            }
263    
264            @Override
265            protected Node newNode() {
266                return new Downdiagonalstrike(this.nodeName, this.ownerDocument);
267            }
268    
269            /** {@inheritDoc} */
270            @Override
271            protected void enclHook(final LayoutInfo info, final LayoutStage stage,
272                    final LayoutContext now) {
273    
274                final Color color = (Color) now.getParameter(Parameter.MATHCOLOR);
275                final float lineWidth = GraphicsSupport.lineWidth(now);
276                info.setGraphicsObject(new LineObject(0, -info
277                        .getAscentHeight(stage), info.getWidth(stage), info
278                        .getDescentHeight(stage), lineWidth, color));
279            }
280        }
281    
282        private static final class Actuarial extends
283                Menclose.AbstractRowLikeNotation {
284            private static final long serialVersionUID = 1L;
285    
286            /**
287             * Default constructor. Sets MathML Namespace.
288             * 
289             * @param qname
290             *            Qualified name.
291             * @param odoc
292             *            Owner Document.
293             */
294            public Actuarial(final String qname, final AbstractDocument odoc) {
295                super(qname, odoc);
296            }
297    
298            @Override
299            protected Node newNode() {
300                return new Actuarial(this.nodeName, this.ownerDocument);
301            }
302    
303            /** {@inheritDoc} */
304            @Override
305            protected Dimension2D getBorderLeftTop(final LayoutContext now) {
306                final float lineSpace = GraphicsSupport.lineWidth(now) * 2;
307                return new Dimension2DImpl(0.0f, lineSpace);
308            }
309    
310            /** {@inheritDoc} */
311            @Override
312            protected Dimension2D getBorderRightBottom(final LayoutContext now) {
313                final float lineSpace = GraphicsSupport.lineWidth(now) * 2;
314                return new Dimension2DImpl(lineSpace, 0.0f);
315            }
316    
317            /** {@inheritDoc} */
318            @Override
319            protected void enclHook(final LayoutInfo info, final LayoutStage stage,
320                    final LayoutContext now) {
321                final List<GraphicsObject> graphicObjects = info
322                        .getGraphicObjects();
323                graphicObjects.clear();
324                final float lineWidth = GraphicsSupport.lineWidth(now);
325                final float top = info.getAscentHeight(stage) + lineWidth;
326                final Color color = (Color) now.getParameter(Parameter.MATHCOLOR);
327                graphicObjects.add(new LineObject(0, -top, info.getWidth(stage)
328                        - lineWidth, -top, lineWidth, color));
329                graphicObjects.add(new LineObject(info.getWidth(stage) - lineWidth,
330                        -top, info.getWidth(stage) - lineWidth, info
331                                .getDescentHeight(stage), lineWidth, color));
332            }
333    
334        }
335    
336        /**
337         * Logger for this class
338         */
339        private static final Log LOGGER = LogFactory.getLog(Menclose.class);
340    
341        private static final Map<String, Constructor<?>> IMPL_CLASSES = new HashMap<String, Constructor<?>>();;
342    
343        private static final long serialVersionUID = 1L;
344    
345        /**
346         * Default constructor. Sets MathML Namespace.
347         * 
348         * @param qname
349         *            Qualified name.
350         * @param odoc
351         *            Owner Document.
352         */
353        public Menclose(final String qname, final AbstractDocument odoc) {
354            super(qname, odoc);
355    
356            this.setDefaultMathAttribute(Menclose.ATTR_NOTATION, Menclose.LONGDIV);
357        }
358    
359        /** {@inheritDoc} */
360        @Override
361        protected Node newNode() {
362            return new Menclose(this.nodeName, this.ownerDocument);
363        }
364    
365        /**
366         * @return notation of menclose element
367         */
368        public String getNotation() {
369            return this.getMathAttribute(Menclose.ATTR_NOTATION);
370        }
371    
372        /**
373         * Sets notation for menclose element.
374         * 
375         * @param notation
376         *            Notation
377         */
378        public void setNotation(final String notation) {
379            this.setAttribute(Menclose.ATTR_NOTATION, notation);
380        }
381    
382        /** {@inheritDoc} */
383        @Override
384        protected List<LayoutableNode> createDelegates() {
385            final Stack<Constructor<?>> notationImpls = this.parseNotations();
386            JEuclidElement lastChild = this.ensureSingleChild();
387            lastChild = this.createStackOfDelegates(notationImpls, lastChild);
388            return Collections.singletonList((LayoutableNode) lastChild);
389        }
390    
391        private JEuclidElement createStackOfDelegates(
392                final Stack<Constructor<?>> notationImpls,
393                final JEuclidElement oldChild) {
394            JEuclidElement lastChild = oldChild;
395            while (!notationImpls.isEmpty()) {
396                final Constructor<?> con = notationImpls.pop();
397                try {
398                    final JEuclidElement element = (JEuclidElement) con
399                            .newInstance("saklsdiwet:menclosechild",
400                                    this.ownerDocument);
401                    element.appendChild(lastChild);
402                    lastChild = element;
403                } catch (final InstantiationException e) {
404                    Menclose.LOGGER.warn(e);
405                } catch (final IllegalAccessException e) {
406                    Menclose.LOGGER.warn(e);
407                } catch (final InvocationTargetException e) {
408                    Menclose.LOGGER.warn(e);
409                }
410            }
411            return lastChild;
412        }
413    
414        /**
415         * This is just to make sure that there is at least one delegate, and that
416         * each of the standard delegates has exactly one child.
417         * 
418         * @return a single JEuclidElement.
419         */
420        private JEuclidElement ensureSingleChild() {
421            final JEuclidElement lastChild;
422            if (this.getMathElementCount() == 1) {
423                lastChild = this.getMathElement(0);
424            } else {
425                lastChild = (JEuclidElement) this.ownerDocument
426                        .createElement(Mrow.ELEMENT);
427                for (final Node child : ElementListSupport
428                        .createListOfChildren(this)) {
429                    lastChild.appendChild(child);
430                }
431            }
432            return lastChild;
433        }
434    
435        private Stack<Constructor<?>> parseNotations() {
436            final String[] notations = this.getNotation().split(" ");
437            final Stack<Constructor<?>> notationImpls = new Stack<Constructor<?>>();
438            for (final String curNotation : notations) {
439                final Constructor<?> con = Menclose.IMPL_CLASSES.get(curNotation
440                        .toLowerCase(Locale.ENGLISH));
441                if (con == null) {
442                    if (curNotation.length() > 0) {
443                        Menclose.LOGGER.info("Unsupported notation for menclose: "
444                                + curNotation);
445                    }
446                } else {
447                    notationImpls.push(con);
448                }
449            }
450            return notationImpls;
451        }
452    
453        static {
454            try {
455                Menclose.IMPL_CLASSES.put("radical", Msqrt.class.getConstructor(
456                        String.class, AbstractDocument.class));
457                Menclose.IMPL_CLASSES.put(Menclose.LONGDIV, Menclose.Longdiv.class
458                        .getConstructor(String.class, AbstractDocument.class));
459                Menclose.IMPL_CLASSES.put("updiagonalstrike",
460                        Menclose.Updiagonalstrike.class.getConstructor(
461                                String.class, AbstractDocument.class));
462                Menclose.IMPL_CLASSES.put("downdiagonalstrike",
463                        Menclose.Downdiagonalstrike.class.getConstructor(
464                                String.class, AbstractDocument.class));
465                Menclose.IMPL_CLASSES.put("actuarial", Menclose.Actuarial.class
466                        .getConstructor(String.class, AbstractDocument.class));
467            } catch (final NoSuchMethodException e) {
468                Menclose.LOGGER.fatal(e);
469            }
470        }
471    }