View Javadoc

1   /*
2    * Copyright 2002 - 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: Mtable.java 310 2007-05-18 20:26:36Z maxberger $ */
18  
19  package net.sourceforge.jeuclid.elements.presentation.table;
20  
21  import java.awt.BasicStroke;
22  import java.awt.Graphics2D;
23  import java.awt.Stroke;
24  import java.awt.geom.Line2D;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Vector;
28  
29  import net.sourceforge.jeuclid.MathBase;
30  import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
31  import net.sourceforge.jeuclid.elements.JEuclidElement;
32  import net.sourceforge.jeuclid.elements.presentation.token.Mn;
33  import net.sourceforge.jeuclid.elements.support.GraphicsSupport;
34  import net.sourceforge.jeuclid.elements.support.attributes.AttributesHelper;
35  
36  import org.w3c.dom.DOMException;
37  import org.w3c.dom.mathml.MathMLLabeledRowElement;
38  import org.w3c.dom.mathml.MathMLNodeList;
39  import org.w3c.dom.mathml.MathMLTableElement;
40  import org.w3c.dom.mathml.MathMLTableRowElement;
41  
42  /**
43   * This class presents a table.
44   * 
45   * @author Unknown
46   * @author Max Berger
47   * @version $Revision: 310 $
48   */
49  public class Mtable extends AbstractJEuclidElement implements
50          MathMLTableElement {
51  
52      /** attribute for rowlines. */
53      public static final String ATTR_ROWLINES = "rowlines";
54  
55      /** attribute for columnlines. */
56      public static final String ATTR_COLUMNLINES = "columnlines";
57  
58      /** attribute for align. */
59      public static final String ATTR_ALIGN = "align";
60  
61      /** attribute for alignmentscope. */
62      public static final String ATTR_ALIGNMENTSCOPE = "alignmentscope";
63  
64      /** attribute for columnwidth. */
65      public static final String ATTR_COLUMNWIDTH = "columnwidth";
66  
67      /** attribute for width. */
68      public static final String ATTR_WIDTH = "width";
69  
70      /** attribute for rowspacing. */
71      public static final String ATTR_ROWSPACING = "rowspacing";
72  
73      /** attribute for columnspacing. */
74      public static final String ATTR_COLUMNSPACING = "columnspacing";
75  
76      /** attribute for frame. */
77      public static final String ATTR_FRAME = "frame";
78  
79      /** attribute for framespacing. */
80      public static final String ATTR_FRAMESPACING = "framespacing";
81  
82      /** attribute for equalrows. */
83      public static final String ATTR_EQUALROWS = "equalrows";
84  
85      /** attribute for equalcolumns. */
86      public static final String ATTR_EQUALCOLUMNS = "equalcolumns";
87  
88      /** attribute for displaystyle. */
89      public static final String ATTR_DISPLAYSTYLE = "displaystyle";
90  
91      /** attribute for side. */
92      public static final String ATTR_SIDE = "side";
93  
94      /** attribute for minlabelspacing. */
95      public static final String ATTR_MINLABELSPACING = "minlabelspacing";
96  
97      /** value for no lines. */
98      public static final String VALUE_NONE = "none";
99  
100     /** value for dashed lines. */
101     public static final String VALUE_DASHED = "dashed";
102 
103     /** value for solid lines. */
104     public static final String VALUE_SOLID = "solid";
105 
106     /**
107      * The XML element from this class.
108      */
109     public static final String ELEMENT = "mtable";
110 
111     /**
112      * Align constant: top align.
113      */
114     public static final int ALIGN_TOP = 0;
115 
116     /**
117      * Align constant: bottom align.
118      */
119     public static final int ALIGN_BOTTOM = 1;
120 
121     /** Attribute for rowalign. */
122     public static final String ATTR_ROWALIGN = "rowalign";
123 
124     /** Attribute for columnalign. */
125     public static final String ATTR_COLUMNALIGN = "columnalign";
126 
127     /** Attribute for groupalign. */
128     public static final String ATTR_GROUPALIGN = "groupalign";
129 
130     /**
131      * Default column spacing.
132      */
133     public static final String DEFAULT_COLUMNSPACING = "0.8em";
134 
135     /**
136      * Default row spacing.
137      */
138     public static final String DEFAULT_ROWSPACING = "1.0ex";
139 
140     /**
141      * Align constant: center align.
142      */
143     public static final int ALIGN_CENTER = 2;
144 
145     /**
146      * Align constant: baseline align.
147      */
148     public static final int ALIGN_BASELINE = 3;
149 
150     /**
151      * Align constant: axis align.
152      */
153     public static final int ALIGN_AXIS = 4;
154 
155     /**
156      * Align constant: left align.
157      */
158     public static final int ALIGN_LEFT = 5;
159 
160     /**
161      * Align constant: right align.
162      */
163     public static final int ALIGN_RIGHT = 6;
164 
165     /**
166      * Align constant: mark align.
167      */
168     public static final int ALIGN_MARK = 11;
169 
170     /**
171      * Align constant: decimal point align.
172      */
173     public static final int ALIGN_DECIMALPOINT = 7;
174 
175     /**
176      * Constant width auto.
177      */
178     public static final int WIDTH_AUTO = -1;
179 
180     /**
181      * Constant width fit.
182      */
183     public static final int WIDTH_FIT = -2;
184 
185     /**
186      * Default frame spacing.
187      */
188     private static final String DEFAULT_FRAMESPACING = "0.4em 0.5ex";
189 
190     /**
191      * Class for line types.
192      */
193     public enum LineType {
194         /** No lines. */
195         NONE,
196         /** Solid line. */
197         SOLID,
198         /** Dashed line. */
199         DASHED;
200 
201         /**
202          * Parse a string and return a linetype.
203          * 
204          * @param s
205          *            the string to parse
206          * @return a line type for this string type
207          */
208         public static LineType parseLineType(final String s) {
209             final LineType retVal;
210             if (s.equalsIgnoreCase(Mtable.VALUE_NONE)) {
211                 retVal = NONE;
212             } else if (s.equalsIgnoreCase(Mtable.VALUE_DASHED)) {
213                 retVal = DASHED;
214             } else {
215                 retVal = SOLID;
216             }
217             return retVal;
218         }
219     };
220 
221     /**
222      * Class for alignment types.
223      */
224     public enum AlignmentType {
225         /** Align to top. */
226         TOP,
227         /** Align to bottom. */
228         BOTTOM,
229         /** Align to center. */
230         CENTER,
231         /** Align to baseline. */
232         BASELINE,
233         /** Align to axis. */
234         AXIS,
235         /** Align to left. */
236         LEFT,
237         /** Align to right. */
238         RIGHT,
239         /** Align to decimalpoint. */
240         DECIMALPOINT,
241         /** Align to alignment markers. */
242         MARK;
243 
244         /**
245          * Parse a string and return a alignment.
246          * 
247          * @param s
248          *            the string to parse
249          * @return an alignment for this string type
250          */
251         public static AlignmentType parseAlignmentType(final String s) {
252             final AlignmentType retVal;
253             if ("top".equalsIgnoreCase(s)) {
254                 retVal = Mtable.AlignmentType.TOP;
255             } else if ("bottom".equalsIgnoreCase(s)) {
256                 retVal = Mtable.AlignmentType.BOTTOM;
257             } else if ("baseline".equalsIgnoreCase(s)) {
258                 retVal = Mtable.AlignmentType.BASELINE;
259             } else if ("axis".equalsIgnoreCase(s)) {
260                 retVal = Mtable.AlignmentType.AXIS;
261             } else if ("left".equalsIgnoreCase(s)) {
262                 retVal = Mtable.AlignmentType.LEFT;
263             } else if ("right".equalsIgnoreCase(s)) {
264                 retVal = Mtable.AlignmentType.RIGHT;
265             } else if ("decimalpoint".equalsIgnoreCase(s)) {
266                 retVal = Mtable.AlignmentType.DECIMALPOINT;
267             } else {
268                 retVal = Mtable.AlignmentType.CENTER;
269             }
270             return retVal;
271         }
272     }
273 
274     /**
275      * Creates a math element.
276      * 
277      * @param base
278      *            The base for the math element tree.
279      */
280     public Mtable(final MathBase base) {
281         super(base);
282         this.setDefaultMathAttribute(Mtable.ATTR_ALIGN, "axis");
283         this.setDefaultMathAttribute(Mtable.ATTR_ROWALIGN, "baseline");
284         this.setDefaultMathAttribute(Mtable.ATTR_COLUMNALIGN, "center");
285         this.setDefaultMathAttribute(Mtable.ATTR_GROUPALIGN, "{left}");
286         this.setDefaultMathAttribute(Mtable.ATTR_ALIGNMENTSCOPE,
287                 MathBase.TRUE);
288         this.setDefaultMathAttribute(Mtable.ATTR_COLUMNWIDTH, "auto");
289         this.setDefaultMathAttribute(Mtable.ATTR_WIDTH, "auto");
290         this.setDefaultMathAttribute(Mtable.ATTR_ROWSPACING,
291                 Mtable.DEFAULT_ROWSPACING);
292         this.setDefaultMathAttribute(Mtable.ATTR_COLUMNSPACING,
293                 Mtable.DEFAULT_COLUMNSPACING);
294         this.setDefaultMathAttribute(Mtable.ATTR_ROWLINES, Mtable.VALUE_NONE);
295         this.setDefaultMathAttribute(Mtable.ATTR_COLUMNLINES,
296                 Mtable.VALUE_NONE);
297         this.setDefaultMathAttribute(Mtable.ATTR_FRAME, Mtable.VALUE_NONE);
298         this.setDefaultMathAttribute(Mtable.ATTR_FRAMESPACING,
299                 Mtable.DEFAULT_FRAMESPACING);
300         this.setDefaultMathAttribute(Mtable.ATTR_EQUALROWS, MathBase.FALSE);
301         this
302                 .setDefaultMathAttribute(Mtable.ATTR_EQUALCOLUMNS,
303                         MathBase.FALSE);
304         this
305                 .setDefaultMathAttribute(Mtable.ATTR_DISPLAYSTYLE,
306                         MathBase.FALSE);
307         this.setDefaultMathAttribute(Mtable.ATTR_SIDE, "right");
308         this.setDefaultMathAttribute(Mtable.ATTR_MINLABELSPACING, "0.8em");
309     }
310 
311     /** {@inheritDoc} */
312     @Override
313     public boolean isChildBlock(final JEuclidElement child) {
314         return false;
315     }
316 
317     /**
318      * 
319      * @return Horizontal frame spacing
320      */
321     protected float getFramespacingh() {
322         if (Mtable.LineType.NONE.equals(this.getFrameAsLineType())) {
323             return 0;
324         }
325         final String spacing = this.getSpaceArrayEntry(
326                 this.getFramespacing(), 0);
327         return AttributesHelper.convertSizeToPt(spacing, this,
328                 AttributesHelper.PT);
329     }
330 
331     /**
332      * 
333      * @return Vertical frame spacing
334      */
335     protected float getFramespacingv() {
336         if (Mtable.LineType.NONE.equals(this.getFrameAsLineType())) {
337             return 0;
338         }
339         final String spacing = this.getSpaceArrayEntry(
340                 this.getFramespacing(), 1);
341         return AttributesHelper.convertSizeToPt(spacing, this,
342                 AttributesHelper.PT);
343     }
344 
345     /**
346      * Paints this element.
347      * 
348      * @param g
349      *            The graphics context to use for painting.
350      * @param posX
351      *            The first left position for painting.
352      * @param posY
353      *            The position of the baseline.
354      */
355     @Override
356     public void paint(final Graphics2D g, float posX, float posY) {
357         super.paint(g, posX, posY);
358         posX = posX + this.getFramespacingh();
359         posY = posY + this.getFramespacingv();
360 
361         int i;
362         int j;
363         final float[] maxrowascentheight = new float[this
364                 .getMathElementCount()];
365         final float[] maxrowdescentheight = new float[this
366                 .getMathElementCount()];
367 
368         for (i = 0; i < this.getMathElementCount(); i++) {
369             maxrowascentheight[i] = this.getMaxRowAscentHeight(g, i);
370             maxrowdescentheight[i] = this.getMaxRowDescentHeight(g, i);
371         }
372 
373         final int maxcolumns = this.getMaxColumnCount();
374         final boolean isAlignGroupsExist = this.getMaxGroupAlignCount() == 0 ? false
375                 : true;
376         final float[] maxcolumnwidth = new float[maxcolumns];
377 
378         for (i = 0; i < maxcolumns; i++) {
379             maxcolumnwidth[i] = this.getMaxColumnWidth(g, i);
380         }
381 
382         final float x1 = posX;
383         float x = x1;
384 
385         posY = posY - this.getAscentHeight(g);
386         final float startY = posY;
387 
388         final List<Float> rowlines = new Vector<Float>();
389         final List<Float> columnlines = new Vector<Float>(maxcolumns);
390 
391         for (i = 0; i < this.getMathElementCount(); i++) {
392             final JEuclidElement row = this.getMathElement(i);
393             posY += maxrowascentheight[i];
394 
395             x = x1;
396             for (j = 0; (j < maxcolumns) && (j < row.getMathElementCount()); j++) {
397                 if (isAlignGroupsExist
398                         && this.getAlignGroups((Mtd) row.getMathElement(j)).length > 0) {
399                     // left alignment
400                     row.getMathElement(j).paint(g, x, posY);
401                 } else {
402                     // PG this code makes table to draw content of the cell in
403                     // the middle of the cell
404                     final float xx = x + maxcolumnwidth[j] / 2
405                             - row.getMathElement(j).getWidth(g) / 2;
406                     row.getMathElement(j).paint(g, xx, posY);
407                 }
408                 final float currentColSpacing = this.getColumnspacing(j);
409                 x += maxcolumnwidth[j];
410                 if ((i == 0) && (j < maxcolumns - 1)) {
411                     // TODO: This only sets columnlines if the first row
412                     // covers all the columns. Maybe this needs to be changed?
413                     columnlines.add(x + currentColSpacing / 2.0f);
414                 }
415                 x += currentColSpacing;
416             }
417 
418             posY += maxrowdescentheight[i];
419             final float currentRowSpacing = this.getRowspacing(i);
420             if (i < (this.getMathElementCount() - 1)) {
421                 rowlines.add(posY + currentRowSpacing / 2.0f);
422             }
423             posY += currentRowSpacing;
424         }
425         int col = 0;
426         final Stroke oldStroke = g.getStroke();
427         final float lineWidth = GraphicsSupport.lineWidth(this);
428         final float dashWidth = 3.0f * lineWidth;
429         final Stroke solidStroke = new BasicStroke(lineWidth);
430         final Stroke dashedStroke = new BasicStroke(lineWidth,
431                 BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, lineWidth,
432                 new float[] { dashWidth, dashWidth }, 0);
433         for (final float lineX : columnlines) {
434             final LineType lt = this.getColumnLine(col);
435             col++;
436             if (Mtable.LineType.SOLID.equals(lt)) {
437                 g.setStroke(solidStroke);
438             } else if (Mtable.LineType.DASHED.equals(lt)) {
439                 g.setStroke(dashedStroke);
440             }
441             if (!Mtable.LineType.NONE.equals(lt)) {
442                 g.draw(new Line2D.Float(lineX, startY, lineX, posY));
443             }
444         }
445         int row = 0;
446         for (final float lineY : rowlines) {
447             // TODO: This only works if the last entry has all column. Maybe
448             // this needs to be changed?
449             final LineType lt = this.getRowLine(row);
450             row++;
451             // TODO: This code is very similar to the one for columnlines.
452             // Refactor!
453             if (Mtable.LineType.SOLID.equals(lt)) {
454                 g.setStroke(solidStroke);
455             } else if (Mtable.LineType.DASHED.equals(lt)) {
456                 g.setStroke(dashedStroke);
457             }
458             if (!Mtable.LineType.NONE.equals(lt)) {
459                 g.draw(new Line2D.Float(x1, lineY, x, lineY));
460             }
461         }
462         g.setStroke(oldStroke);
463     }
464 
465     /**
466      * Returns the maximal ascent height of a row in this table.
467      * 
468      * @param row
469      *            Row.
470      * @return Maximal ascent height.
471      */
472     private float getMaxRowAscentHeight(final Graphics2D g, final int row) {
473         if (row >= this.getMathElementCount()) {
474             return 0;
475         }
476         final JEuclidElement child = this.getMathElement(row);
477         float height = 0;
478 
479         for (int i = 0; i < child.getMathElementCount(); i++) {
480             height = Math.max(height, child.getMathElement(i)
481                     .getAscentHeight(g));
482         }
483         return height;
484     }
485 
486     /**
487      * Returns the maximal descent height of a row in this table.
488      * 
489      * @param row
490      *            Row.
491      * @return Maximal descent height.
492      */
493     private float getMaxRowDescentHeight(final Graphics2D g, final int row) {
494         if (row >= this.getMathElementCount()) {
495             return 0;
496         }
497 
498         final JEuclidElement child = this.getMathElement(row);
499         float height = 0;
500 
501         for (int i = 0; i < child.getMathElementCount(); i++) {
502             height = Math.max(height, child.getMathElement(i)
503                     .getDescentHeight(g));
504         }
505         return height;
506     }
507 
508     /**
509      * Returns the maximal width of a column in this table.
510      * 
511      * @param column
512      *            Column.
513      * @return Maximal width.
514      */
515     private float getMaxColumnWidth(final Graphics2D g, final int column) {
516         float width = 0;
517 
518         for (int i = 0; i < this.getMathElementCount(); i++) {
519             final JEuclidElement child = this.getMathElement(i); // row
520 
521             if (column < child.getMathElementCount()) {
522                 width = Math.max(width, child.getMathElement(column)
523                         .getWidth(g));
524             }
525         }
526         return width;
527     }
528 
529     /**
530      * Returns the maximal count of columns.
531      * 
532      * @return Maximal count of columns.
533      */
534     private int getMaxColumnCount() {
535         int count = 0;
536 
537         for (int i = 0; i < this.getMathElementCount(); i++) {
538             final JEuclidElement child = this.getMathElement(i);
539             count = Math.max(count, child.getMathElementCount());
540         }
541         return count;
542     }
543 
544     /** {@inheritDoc} */
545     @Override
546     public float calculateWidth(final Graphics2D g) {
547         this.calculateAlignmentGroups(g);
548         float width = 0;
549         final int maxcolumns = this.getMaxColumnCount();
550 
551         for (int i = 0; i < maxcolumns; i++) {
552             width = width + this.getMaxColumnWidth(g, i);
553             if (i + 1 < maxcolumns) {
554                 width = width + this.getColumnspacing(i);
555             }
556         }
557         width = width + this.getFramespacingh() * 2;
558         return width;
559     }
560 
561     private float calculateActualHeight(final Graphics2D g) {
562         float height = 0;
563         final int mec = this.getMathElementCount();
564         for (int i = 0; i < mec; i++) {
565             height = height + this.getMaxRowAscentHeight(g, i)
566                     + this.getMaxRowDescentHeight(g, i);
567             if (i + 1 < mec) {
568                 height = height + this.getRowspacing(i);
569             }
570         }
571         height = height + this.getFramespacingv() * 2;
572         return height;
573     }
574 
575     /** {@inheritDoc} */
576     @Override
577     public float calculateAscentHeight(final Graphics2D g) {
578         final AlignmentType align = Mtable.AlignmentType
579                 .parseAlignmentType(this.getAlign());
580         if (Mtable.AlignmentType.BOTTOM.equals(align)) {
581             return this.calculateActualHeight(g);
582         } else if (Mtable.AlignmentType.TOP.equals(align)) {
583             return this.getRowCount() > 0 ? this.getMaxRowAscentHeight(g, 0)
584                     : 0;
585         } else if (Mtable.AlignmentType.AXIS.equals(align)) {
586             // add +1 to eliminate rounding errors
587             return (this.calculateActualHeight(g) + 1) / 2;
588         }
589         // baseline or center
590         // add +1 to eliminate rounding errors
591         return (this.calculateActualHeight(g) + 1) / 2
592                 + this.getMiddleShift(g);
593     }
594 
595     /** {@inheritDoc} */
596     @Override
597     public float calculateDescentHeight(final Graphics2D g) {
598         final Mtable.AlignmentType align = Mtable.AlignmentType
599                 .parseAlignmentType(this.getAlign());
600         if (Mtable.AlignmentType.BOTTOM.equals(align)) {
601             return 0;
602         } else if (Mtable.AlignmentType.TOP.equals(align)) {
603             return this.calculateActualHeight(g)
604                     - (this.getRowCount() > 0 ? this.getMaxRowAscentHeight(g,
605                             0) : 0);
606         } else if (Mtable.AlignmentType.AXIS.equals(align)) {
607             // add +1 to eliminate rounding errors
608             return (this.calculateActualHeight(g) + 1) / 2;
609         }
610         final float b = this.getMiddleShift(g);
611         // add +1 to eliminate rounding errors
612         final float c = (this.calculateActualHeight(g) + 1) / 2;
613         return c - b;
614     }
615 
616     /*
617      * ----------------- NEW FUNCTIONS, CONCERNED <MALIGNGROUP> ELEMENT
618      * -------------------
619      */
620 
621     /**
622      * Retrieves groupalign values from mtd element. If requested cell doesn't
623      * contain groupalign attribute, the current row (and after it current
624      * table) will be requested.
625      * 
626      * @param cell
627      *            Cell to get groupalign values.
628      * @return Array with groupalign values.
629      */
630     public List<Mtable.AlignmentType> getGroupAlign(final Mtd cell) {
631         final List<Mtable.AlignmentType> result = new Vector<Mtable.AlignmentType>();
632 
633         String groupAlign;
634 
635         if (cell == null) {
636             return result;
637         }
638         final String cellGroupAlign = cell.getGroupalign();
639         if (cellGroupAlign == null || cellGroupAlign.length() == 0) {
640             groupAlign = ((Mtr) cell.getParent()).getGroupalign();
641         } else {
642             groupAlign = cell.getGroupalign();
643         }
644 
645         if (groupAlign == null) {
646             groupAlign = this.getGroupalign();
647         }
648 
649         if (groupAlign != null) {
650 
651             final String[] gAlign = groupAlign.split("\\w");
652             for (final String value : gAlign) {
653                 if (value.length() > 0) {
654                     result
655                             .add(Mtable.AlignmentType
656                                     .parseAlignmentType(value));
657                 }
658             }
659         }
660 
661         return result;
662     }
663 
664     /**
665      * Get max count of align groups in a cell.
666      * 
667      * @return Max number of groups.
668      */
669     private int getMaxGroupAlignCount() {
670         int result = 0;
671         final int rowsCount = this.getRowCount();
672         final int columnsCount = this.getMaxColumnCount();
673         int tmpLength = 0;
674 
675         for (int column = 0; column < columnsCount; column++) {
676             for (int row = 0; row < rowsCount; row++) {
677                 Mtd cell = null;
678                 try {
679                     cell = this.getCell(row, column);
680                 } catch (final Exception e) {
681                     return 0;
682                 }
683 
684                 if (cell != null) {
685                     tmpLength = cell.getAlignGroups().size();
686                     if (result < tmpLength) {
687                         result = tmpLength;
688                     }
689                 }
690             }
691         }
692 
693         return result;
694     }
695 
696     /**
697      * Gets count of rows in table.
698      * 
699      * @return
700      */
701     private int getRowCount() {
702         return this.getMathElementCount();
703     }
704 
705     /**
706      * Finds cell in the table by row and column indexes.
707      * 
708      * @param row
709      *            Row index.
710      * @param column
711      *            Column index.
712      * @return Cell object.
713      * @throws Exception
714      *             Throw exception, if table doesn't contain <mtr> and <mtd>
715      *             tags.
716      */
717     private Mtd getCell(final int row, final int column) throws Exception {
718         final int rowsCount = this.getRowCount();
719         final int columnsCount = this.getMaxColumnCount();
720         Mtd cell = null;
721 
722         if (row > rowsCount - 1 || column > columnsCount - 1) {
723             return cell;
724         }
725 
726         final JEuclidElement theRow = this.getMathElement(row); // row
727         if (column < theRow.getMathElementCount()) {
728             if (theRow.getMathElement(column) instanceof Mtd) {
729                 cell = (Mtd) theRow.getMathElement(column);
730             } else {
731                 throw new Exception(
732                         "This table doesn't contain <mtr> and <mtd> tags.");
733             }
734         }
735 
736         return cell;
737     }
738 
739     /**
740      * Determines all maligngroup elements in the cell.
741      * 
742      * @param cell
743      *            Cell object.
744      * @return Array of maligngroup elements.
745      */
746     private Maligngroup[] getAlignGroups(final Mtd cell) {
747         Maligngroup[] result;
748 
749         final List<Maligngroup> list = cell.getAlignGroups();
750 
751         if (list.size() == 0) {
752             result = new Maligngroup[0];
753         } else {
754             // copy to result array
755             result = new Maligngroup[list.size()];
756             for (int i = 0; i < list.size(); i++) {
757                 result[i] = (list.get(i));
758             }
759         }
760 
761         return result;
762     }
763 
764     /**
765      * Constant for calculating of align elements widths.
766      */
767     private static final int WHOLE_WIDTH = 0;
768 
769     /**
770      * Constant for calculating of align elements widths.
771      */
772     private static final int LEFT_WIDTH = 1;
773 
774     /**
775      * Constant for calculating of align elements widths.
776      */
777     private static final int RIGHT_WIDTH = 2;
778 
779     /**
780      * Method calculates widths of alignment elements. Must be called after
781      * the calculating of all MathElements widths.
782      * 
783      * @param g
784      *            Graphics2D context to use.
785      */
786     protected void calculateAlignmentGroups(final Graphics2D g) {
787         final int rowsCount = this.getRowCount();
788         final int columnsCount = this.getMaxColumnCount();
789         final int MAX_WIDTH_IN_COLUMN = rowsCount;
790         final int maxGroupAlignCount = this.getMaxGroupAlignCount();
791         if (maxGroupAlignCount == 0) {
792             return;
793         }
794         // structure to hold calculated temp values, for each element from
795         // align
796         // group
797         // [0] whole width value
798         // [1] left width value (till malignmark or decimal point)
799         // [2] right width value
800         // rowsCount+1 - max width of element (or its part)
801         final float[][][][] alignwidths = new float[columnsCount][3][rowsCount + 1][maxGroupAlignCount];
802         // need to know, either this column of aligngroup uses malignmarks
803         final boolean[][] usesMarks = new boolean[columnsCount][this
804                 .getMaxGroupAlignCount()];
805         // array of aligngropus of the cell
806         Maligngroup[] aligngroups; // align values of aligngroups in the
807         // cell
808         List<Mtable.AlignmentType> groupalignvalues; // elements of the
809         // aligngroup
810         List<JEuclidElement> elements;
811         for (int col = 0; col < columnsCount; col++) { // walking through the
812             // column "column"
813             for (int row = 0; row < rowsCount; row++) {
814                 Mtd cell = null;
815                 try {
816                     cell = this.getCell(row, col);
817                 } catch (final Exception e) {
818                     cell = null;
819                 }
820                 if (cell != null) { // all align components of the cell
821                     aligngroups = this.getAlignGroups(cell);
822                     groupalignvalues = this.getGroupAlign(cell);
823                     if (groupalignvalues.size() == 0) {
824                         // values of alignment didn't mentioned, have to use
825                         // default
826                         groupalignvalues = new Vector<Mtable.AlignmentType>(
827                                 aligngroups.length);
828                         for (final Maligngroup element : aligngroups) {
829                             groupalignvalues.add(Mtable.AlignmentType.LEFT);
830                         }
831                     }
832                     if (aligngroups.length == 0
833                             || aligngroups.length < groupalignvalues.size()) {
834                         continue; // there is no aligngroups in the cell or
835                         // wrong count
836                     }
837                     for (int alignIndex = 0; alignIndex < groupalignvalues
838                             .size(); alignIndex++) {
839                         // widths of align group
840                         elements = Maligngroup
841                                 .getElementsOfAlignGroup(aligngroups[alignIndex]);
842                         alignwidths[col][Mtable.WHOLE_WIDTH][row][alignIndex] = Maligngroup
843                                 .getElementsWholeWidth(g, elements);
844                         // max whole width of group
845                         if (alignwidths[col][Mtable.WHOLE_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] < alignwidths[col][Mtable.WHOLE_WIDTH][row][alignIndex]) {
846                             alignwidths[col][Mtable.WHOLE_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] = alignwidths[col][Mtable.WHOLE_WIDTH][row][alignIndex];
847                         }
848                         // there is alignmark in the group
849                         if (aligngroups[alignIndex].getMark() != null) {
850                             usesMarks[col][alignIndex] = true;
851                             final float leftPart = this.getWidthTillMark(g,
852                                     elements.iterator());
853                             // left width = width till alignmark
854                             alignwidths[col][Mtable.LEFT_WIDTH][row][alignIndex] = leftPart;
855                             // right width = whole width - left width
856                             alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex] = alignwidths[col][Mtable.WHOLE_WIDTH][row][alignIndex]
857                                     - leftPart;
858                             // max left width
859                             if (alignwidths[col][Mtable.LEFT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] < leftPart) {
860                                 alignwidths[col][Mtable.LEFT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] = leftPart;
861                             }
862                             // max right width
863                             if (alignwidths[col][Mtable.RIGHT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] < alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex]) {
864                                 alignwidths[col][Mtable.RIGHT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] = alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex];
865                             }
866                         } else {
867                             // there is no alignmark, right width is equal to
868                             // the whole width
869                             alignwidths[col][Mtable.LEFT_WIDTH][row][alignIndex] = 0;
870                             alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex] = alignwidths[col][Mtable.WHOLE_WIDTH][row][alignIndex];
871                             // but! may be, we have alignment decimalpoint...
872                             if (groupalignvalues.get(alignIndex).equals(
873                                     Mtable.AlignmentType.DECIMALPOINT)) {
874                                 // width of the left (till decimal point)
875                                 // value
876                                 // of MathNumber
877                                 final float tillPoint = this
878                                         .getWidthTillPoint(g, elements
879                                                 .iterator());
880                                 final float pointWidth = this.getPointWidth(
881                                         g, elements.iterator());
882                                 // left width
883                                 alignwidths[col][Mtable.LEFT_WIDTH][row][alignIndex] = tillPoint;
884                                 // determine max left width till decimal point
885                                 if (alignwidths[col][Mtable.LEFT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] < tillPoint) {
886                                     alignwidths[col][Mtable.LEFT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] = tillPoint;
887                                 }
888                                 // calculate right part
889                                 alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex] = alignwidths[col][Mtable.WHOLE_WIDTH][row][alignIndex]
890                                         - tillPoint - pointWidth;
891                                 // calculate right max
892                                 if (alignwidths[col][Mtable.RIGHT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] < alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex]) {
893                                     alignwidths[col][Mtable.RIGHT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex] = alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex];
894                                 }
895                             }
896                         } // no align mark in this align group
897                     } // cycle: align groups in the cell
898                 } // if cell != null
899             } // cycle: rows
900         } // cycle: columns
901 
902         // calculating shifts of MathAlignGroup elements
903         Mtable.AlignmentType alignOfTheGroup = Mtable.AlignmentType.LEFT;
904         float currentWidth = 0;
905         float maxWidth = 0;
906         float leftWidth = 0;
907         float leftMaxWidth = 0;
908         float rightWidth = 0;
909         float rightMaxWidth = 0;
910         Maligngroup group = null;
911         Maligngroup nextGroup = null;
912         for (int col = 0; col < columnsCount; col++) {
913             for (int row = 0; row < rowsCount; row++) {
914                 Mtd cell = null;
915                 try {
916                     cell = this.getCell(row, col);
917                 } catch (final Exception e) {
918                     cell = null;
919                 }
920 
921                 if (cell != null) {
922                     aligngroups = this.getAlignGroups(cell);
923                     groupalignvalues = this.getGroupAlign(cell);
924                     if (groupalignvalues.size() == 0) {
925                         // values of alignment didn't mentioned, have to use
926                         // default
927                         groupalignvalues = new Vector<Mtable.AlignmentType>(
928                                 aligngroups.length);
929                         for (final Maligngroup element : aligngroups) {
930                             groupalignvalues.add(Mtable.AlignmentType.LEFT);
931                         }
932                     }
933                     if (aligngroups.length == 0
934                             || aligngroups.length < groupalignvalues.size()) {
935                         continue;
936                     }
937                     for (int alignIndex = 0; alignIndex < groupalignvalues
938                             .size(); alignIndex++) {
939                         // retrieving previously calculated information about
940                         // aligngroups widths
941                         alignOfTheGroup = groupalignvalues.get(alignIndex);
942                         currentWidth = alignwidths[col][Mtable.WHOLE_WIDTH][row][alignIndex];
943                         maxWidth = alignwidths[col][Mtable.WHOLE_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex];
944                         leftWidth = alignwidths[col][Mtable.LEFT_WIDTH][row][alignIndex];
945                         leftMaxWidth = alignwidths[col][Mtable.LEFT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex];
946                         rightWidth = alignwidths[col][Mtable.RIGHT_WIDTH][row][alignIndex];
947                         rightMaxWidth = alignwidths[col][Mtable.RIGHT_WIDTH][MAX_WIDTH_IN_COLUMN][alignIndex];
948                         group = aligngroups[alignIndex];
949                         if (usesMarks[col][alignIndex]) {
950                             alignOfTheGroup = Mtable.AlignmentType.MARK;
951                         }
952                         if (alignIndex < groupalignvalues.size() - 1) {
953                             nextGroup = aligngroups[alignIndex + 1];
954                         }
955                         switch (alignOfTheGroup) {
956                         case RIGHT:
957                             group.width += maxWidth - currentWidth;
958                             break;
959                         case LEFT:
960                             if (alignIndex < groupalignvalues.size() - 1) {
961                                 nextGroup.width += maxWidth - currentWidth;
962                             }
963                             break;
964                         case CENTER:
965                             group.width += (maxWidth - currentWidth) / 2;
966                             if (alignIndex < groupalignvalues.size() - 1) {
967                                 nextGroup.width += (maxWidth - currentWidth) / 2;
968                             }
969                             break;
970                         case DECIMALPOINT:
971                         case MARK:
972                             group.width += (leftMaxWidth - leftWidth);
973                             if (alignIndex < groupalignvalues.size() - 1) {
974                                 nextGroup.width += rightMaxWidth - rightWidth;
975                             }
976                             break;
977                         default:
978                             group.width += maxWidth - currentWidth;
979                         }
980                     }
981                 }
982             }
983         }
984     }
985 
986     /**
987      * Iterator over a NodeList.
988      * 
989      * @author Max Berger
990      */
991     private class ChildIterator implements Iterator {
992 
993         private final org.w3c.dom.NodeList nodeList;
994 
995         private int pos;
996 
997         protected ChildIterator(final org.w3c.dom.NodeList nl) {
998             this.nodeList = nl;
999         }
1000 
1001         public boolean hasNext() {
1002             return this.pos < this.nodeList.getLength();
1003         }
1004 
1005         public Object next() {
1006             this.pos++;
1007             return this.nodeList.item(this.pos - 1);
1008         }
1009 
1010         public void remove() {
1011             // not needed.
1012         }
1013 
1014     }
1015 
1016     /**
1017      * Method calculates width of elements till point inside of mn element (or
1018      * first of mn-element).
1019      * 
1020      * @param elements
1021      *            List of elements.
1022      * @return Width of elements till point.
1023      */
1024     private float getWidthTillPoint(final Graphics2D g,
1025             final Iterator elements) {
1026         float result = 0;
1027 
1028         JEuclidElement element = null;
1029         for (; elements.hasNext();) {
1030             element = (JEuclidElement) elements.next();
1031             if (element instanceof Mn) {
1032                 return result + ((Mn) element).getWidthTillPoint(g);
1033             } else {
1034                 if (!this.containsNumber(element)) {
1035                     result += element.getWidth(g);
1036                 } else {
1037                     result += this.getWidthTillPoint(g, new ChildIterator(
1038                             this.getChildNodes()));
1039 
1040                 }
1041             }
1042         }
1043 
1044         return result;
1045     }
1046 
1047     /*
1048      * Checks, whether provided element contains any mn - element. @return
1049      * True, if contains any mn element.
1050      */
1051     private boolean containsNumber(final JEuclidElement element) {
1052         return this.containsElement(element, Mn.ELEMENT);
1053     }
1054 
1055     /**
1056      * Finds in the list of the element mn element and requests it for the
1057      * with of the '.' character (used for the "decimalpoint" alignment).
1058      * 
1059      * @param iterator
1060      *            List of elements.
1061      * @return Width of the point.
1062      */
1063     private float getPointWidth(final Graphics2D g, final Iterator iterator) {
1064         float result = 0;
1065 
1066         for (; iterator.hasNext();) {
1067             final JEuclidElement element = (JEuclidElement) iterator.next();
1068             if (element instanceof Mn) {
1069                 result = ((Mn) element).getPointWidth(g);
1070                 break;
1071             }
1072         }
1073 
1074         return result;
1075     }
1076 
1077     /**
1078      * Method calculates width of elements till malignmark element.
1079      * 
1080      * @param elements
1081      *            List of elements.
1082      * @return Width of elements till malignmar.
1083      */
1084     private float getWidthTillMark(final Graphics2D g, final Iterator elements) {
1085         float result = 0;
1086 
1087         JEuclidElement element = null;
1088         for (; elements.hasNext();) {
1089             element = (JEuclidElement) elements.next();
1090             if (element instanceof Malignmark) {
1091                 return result;
1092             } else {
1093                 if (!this.containsMark(element)) {
1094                     result += element.getWidth(g);
1095                 } else {
1096                     result += this.getWidthTillMark(g, new ChildIterator(this
1097                             .getChildNodes()));
1098                 }
1099             }
1100         }
1101 
1102         return result;
1103     }
1104 
1105     /**
1106      * Method checks, whether provided element contains malignmark element.
1107      * 
1108      * @param element
1109      *            Container object.
1110      * @return True, if element contains any malignmark object.
1111      */
1112     private boolean containsMark(final JEuclidElement element) {
1113         return this.containsElement(element, Malignmark.ELEMENT);
1114     }
1115 
1116     /**
1117      * Method checks, whether provided container contains element of type, as
1118      * searchName.
1119      * 
1120      * @param container
1121      *            Container object.
1122      * @param searchName
1123      *            Type of element to look for.
1124      * @return True, if container contains such type of element.
1125      */
1126     private boolean containsElement(final JEuclidElement container,
1127             final String searchName) {
1128         if (container.getTagName().equals(searchName)) {
1129             return true;
1130         }
1131 
1132         for (int i = 0; i < container.getMathElementCount(); i++) {
1133             if (this.containsElement(container.getMathElement(i), searchName)) {
1134                 return true;
1135             }
1136         }
1137 
1138         return false;
1139     }
1140 
1141     /** {@inheritDoc} */
1142     public String getTagName() {
1143         return Mtable.ELEMENT;
1144     }
1145 
1146     /** {@inheritDoc} */
1147     public String getRowlines() {
1148         return this.getMathAttribute(Mtable.ATTR_ROWLINES);
1149     }
1150 
1151     /** {@inheritDoc} */
1152     public void setRowlines(final String rowlines) {
1153         this.setAttribute(Mtable.ATTR_ROWLINES, rowlines);
1154     }
1155 
1156     /** {@inheritDoc} */
1157     public String getColumnlines() {
1158         return this.getMathAttribute(Mtable.ATTR_COLUMNLINES);
1159     }
1160 
1161     /** {@inheritDoc} */
1162     public void setColumnlines(final String columnlines) {
1163         this.setAttribute(Mtable.ATTR_COLUMNLINES, columnlines);
1164     }
1165 
1166     private LineType getRowLine(final int row) {
1167         return Mtable.LineType.parseLineType(this.getSpaceArrayEntry(this
1168                 .getRowlines(), row));
1169     }
1170 
1171     private LineType getColumnLine(final