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: MathBase.java 571 2007-12-12 14:11:46Z maxberger $ */
018    
019    package net.sourceforge.jeuclid;
020    
021    import java.awt.Graphics2D;
022    import java.awt.RenderingHints;
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import net.sourceforge.jeuclid.elements.generic.DocumentElement;
027    
028    /**
029     * Keeps a MathML Tree and its Rendering attributes.
030     * <p>
031     * This is the main class for MathML handling. It stores a MathML Tree and its
032     * rendering attributes.
033     * <p>
034     * To obtain a renderable MathML tree, create an instance of this class, and
035     * fill its tree with the help of {@link DOMBuilder}.
036     * 
037     * @author Max Berger
038     * @author <a href="mailto:stephan@vern.chem.tu-berlin.de">Stephan Michels</a>
039     * @author <a href="mailto:sielaff@vern.chem.tu-berlin.de">Marco Sielaff</a>
040     * @version $Revision: 571 $
041     */
042    public class MathBase {
043        /**
044         * Logger for this class. unused.
045         */
046        // private static final Log LOGGER = LogFactory.getLog(MathBase.class);
047        /**
048         * Inline mathematical expression.
049         */
050        public static final int INLINE = 0;
051    
052        /**
053         * Non inline mathematical expression.
054         */
055        public static final int DISPLAY = 1;
056    
057        /**
058         * Default font size.
059         */
060        public static final float DEFAULT_FONTSIZE = 12.0f;
061    
062        /** Constant for string "true". */
063        public static final String TRUE = Boolean.TRUE.toString();
064    
065        /** Constant for string "false". */
066        public static final String FALSE = Boolean.FALSE.toString();
067    
068        /** Constant for zero-value (0). */
069        public static final String VALUE_ZERO = "0";
070    
071        /**
072         * Reference to the root element of mathelements tree.
073         */
074        private DocumentElement rootElement;
075    
076        private final Map<ParameterKey, String> renderParams;
077    
078        /**
079         * Default constructor.
080         * <p>
081         * Allocates a new MathBase with the given rendering parameters. You may
082         * use {@link #getDefaultParameters()} to obtain a default set of
083         * rendering parameters.
084         * <p>
085         * The root element will initially be empty. You may use
086         * {@link DOMBuilder} or {@link SAXBuilder} to fill it.
087         * 
088         * @param params
089         *            Rendering parameters.
090         * @see ParameterKey
091         * @see #getDefaultParameters()
092         */
093        public MathBase(final Map<ParameterKey, String> params) {
094            this.renderParams = MathBase.getDefaultParameters();
095            if (params != null) {
096                this.renderParams.putAll(params);
097            }
098            this.rootElement = new DocumentElement(this);
099        }
100    
101        /**
102         * Gets the height of the ascender.
103         * 
104         * @return Ascent height
105         * @param g
106         *            Graphics2D context to use.
107         */
108        public float getAscender(final Graphics2D g) {
109            return (float) Math.ceil(this.rootElement.getAscentHeight(g));
110        }
111    
112        /**
113         * Returns the height of the descender.
114         * 
115         * @return Descent height
116         * @param g
117         *            Graphics2D context to use.
118         */
119        public float getDescender(final Graphics2D g) {
120            return (float) Math.ceil(this.rootElement.getDescentHeight(g));
121        }
122    
123        /**
124         * Set the root element of a math tree.
125         * 
126         * @param element
127         *            Root element of a math tree
128         */
129        public void setRootElement(final DocumentElement element) {
130            if (element == null) {
131                return;
132            }
133            this.rootElement = element;
134            this.rootElement.setMathBase(this);
135        }
136    
137        /**
138         * @return the Document element associated with this mathbase.
139         */
140        public DocumentElement getRootElement() {
141            return this.rootElement;
142        }
143    
144        /**
145         * Enables, or disables the debug mode.
146         * 
147         * @param debug
148         *            Debug mode flag.
149         */
150        public void setDebug(final boolean debug) {
151            this.renderParams
152                    .put(ParameterKey.DebugMode, Boolean.toString(debug));
153        }
154    
155        /**
156         * Indicates, weither the debug mode is enabled.
157         * 
158         * @return True, if the debug mode is enabled
159         */
160        public boolean isDebug() {
161            return Boolean.parseBoolean(this.renderParams
162                    .get(ParameterKey.DebugMode));
163        }
164    
165        /**
166         * Paints this component and all of its elements.
167         * 
168         * @param g
169         *            The graphics context to use for painting.
170         * @param x
171         *            x-offset
172         * @param y
173         *            y-offset
174         */
175        public void paint(final Graphics2D g, final float x, final float y) {
176            if (this.rootElement != null) {
177                final RenderingHints hints = g.getRenderingHints();
178                if (Boolean.parseBoolean(this.renderParams
179                        .get(ParameterKey.AntiAlias))) {
180                    hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
181                            RenderingHints.VALUE_ANTIALIAS_ON));
182                }
183                hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL,
184                        RenderingHints.VALUE_STROKE_NORMALIZE));
185                hints.add(new RenderingHints(RenderingHints.KEY_RENDERING,
186                        RenderingHints.VALUE_RENDER_QUALITY));
187                g.setRenderingHints(hints);
188                this.rootElement.paint(g, x, y
189                        + (float) Math.ceil(this.rootElement.getAscentHeight(g)));
190            }
191        }
192    
193        /**
194         * Paints the componet and all of its elements into the top-right corner.
195         * 
196         * @param g
197         *            The graphics context to use for painting.
198         * @see #paint(Graphics2D, float, float)
199         */
200        public void paint(final Graphics2D g) {
201            this.paint(g, 0, 0);
202        }
203    
204        /**
205         * Return the current width of this component.
206         * 
207         * @return Width
208         * @param g
209         *            Graphics2D context to use.
210         */
211        public float getWidth(final Graphics2D g) {
212            final float realWidth;
213            if (this.rootElement != null) {
214                realWidth = this.rootElement.getWidth(g);
215            } else {
216                realWidth = 0f;
217            }
218            return Math.max(1.0f, realWidth);
219        }
220    
221        /**
222         * Return the current height of this component.
223         * 
224         * @return Height
225         * @param g
226         *            Graphics2D context to use.
227         */
228        public float getHeight(final Graphics2D g) {
229            final float realHeight;
230            if (this.rootElement != null) {
231                realHeight = (float) (Math.ceil(this.rootElement
232                        .getAscentHeight(g)) + Math.ceil(this.rootElement
233                        .getDescentHeight(g)));
234            } else {
235                realHeight = 0f;
236            }
237            return Math.max(1.0f, realHeight);
238        }
239    
240        /**
241         * @return the fontSize
242         */
243        public float getFontSize() {
244            return Float.parseFloat(this.getParams().get(ParameterKey.FontSize));
245        }
246    
247        /**
248         * Retrieves the current set of parametes.
249         * <p>
250         * Please note that it is not recommended to change any of these
251         * parameters, but rather to use {@link #setParam(ParameterKey, String)}
252         * instead.
253         * 
254         * @return The current set of rendering parameters.
255         */
256        public Map<ParameterKey, String> getParams() {
257            return this.renderParams;
258        }
259    
260        /**
261         * Sets a rendering parameter.
262         * 
263         * @param key
264         *            Key of the rendering parameter.
265         * @param value
266         *            new value.
267         */
268        public void setParam(final ParameterKey key, final String value) {
269            this.renderParams.put(key, value);
270            this.rootElement.fireChangeForSubTree();
271        }
272    
273        /**
274         * Provides a reasonable set of default parameters.
275         * 
276         * @return a set that can be used in {@link #MathBase(Map)}
277         */
278        public static Map<ParameterKey, String> getDefaultParameters() {
279            final Map<ParameterKey, String> params = new HashMap<ParameterKey, String>();
280            params.put(ParameterKey.FontSize, Float
281                    .toString(MathBase.DEFAULT_FONTSIZE));
282            params.put(ParameterKey.DebugMode, MathBase.FALSE);
283            params.put(ParameterKey.OutFileType, "image/png");
284            params.put(ParameterKey.AntiAlias, MathBase.TRUE);
285            params.put(ParameterKey.ForegroundColor, "black");
286            params.put(ParameterKey.BackgroundColor, "transparent");
287    
288            // CHECKSTYLE:OFF
289            final String symbolCatchFonts = "OpenSymbol," + "Standard Symbols L,"
290                    + "Symbol," + "Webdings," + "Wingdings," + "Wingdings 2,"
291                    + "Wingdings 3," + "Arial Unicode MS," + "DejaVu Sans"
292                    + "Cambria Math" + "STIXGeneral";
293            params.put(ParameterKey.FontsSanserif, "Verdana," + "Helvetica,"
294                    + "Arial," + "Arial Unicode MS," + "Lucida Sans Unicode,"
295                    + "Lucida Sans," + "Lucida Grande," + "DejaVu Sans,"
296                    + "Bitstream Vera Sans," + "Luxi Sans," + "FreeSans,"
297                    + "sansserif," + symbolCatchFonts);
298            params.put(ParameterKey.FontsSerif, "Constantina," + "Times,"
299                    + "Times New Roman," + "Lucida Bright," + "DejaVu Serif,"
300                    + "Bitstream Vera Serif," + "Luxi Serif," + "FreeSerif,"
301                    + "serif," + symbolCatchFonts);
302            params.put(ParameterKey.FontsMonospaced, "Andale Mono," + "Courier,"
303                    + "Courier Mono," + "Courier New,"
304                    + "Lucida Sans Typewriter," + "DejaVu Sans Mono,"
305                    + "Bitstream Vera Sans Mono," + "Luxi Mono," + "FreeMono,"
306                    + "monospaced," + symbolCatchFonts);
307            params.put(ParameterKey.FontsScript, "Savoye LET,"
308                    + "Brush Script MT," + "Zapfino," + "Apple Chancery,"
309                    + "Edwardian Script ITC," + "Lucida Handwriting,"
310                    + "Monotype Corsiva," + "Santa Fe LET," + symbolCatchFonts);
311            params
312                    .put(ParameterKey.FontsFraktur, "FetteFraktur,"
313                            + "Fette Fraktur," + "Euclid Fraktur,"
314                            + "Lucida Blackletter," + "Blackmoor LET,"
315                            + symbolCatchFonts);
316            params.put(ParameterKey.FontsDoublestruck, "Caslon Open Face,"
317                    + "Caslon Openface," + "Cloister Open Face,"
318                    + "Academy Engraved LET," + "Colonna MT,"
319                    + "Imprint MT Shadow," + symbolCatchFonts);
320            // CHECKSTYLE:ON
321            return params;
322        }
323    }