/* MatrixAtom.java
* =========================================================================
* This file is part of the JLaTeXMath Library - http://forge.scilab.org/jlatexmath
*
* Copyright (C) 2009 DENIZET Calixte
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* A copy of the GNU General Public License can be found in the file
* LICENSE.txt provided with the source distribution of this program (see
* the META-INF directory in the source jar). This license can also be
* found on the GNU website at http://www.gnu.org/licenses/gpl.html.
*
* If you did not receive a copy of the GNU General Public License along
* with this program, contact the lead developer, or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* Linking this library statically or dynamically with other modules
* is making a combined work based on this library. Thus, the terms
* and conditions of the GNU General Public License cover the whole
* combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce
* an executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under terms
* of your choice, provided that you also meet, for each linked independent
* module, the terms and conditions of the license of that module.
* An independent module is a module which is not derived from or based
* on this library. If you modify this library, you may extend this exception
* to your version of the library, but you are not obliged to do so.
* If you do not wish to do so, delete this exception statement from your
* version.
*
*/
package com.himamis.retex.renderer.share;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.himamis.retex.renderer.share.platform.FactoryProvider;
import com.himamis.retex.renderer.share.platform.geom.Rectangle2D;
import com.himamis.retex.renderer.share.platform.graphics.Color;
/**
* A box representing a matrix.
*/
@SuppressWarnings("javadoc")
public class MatrixAtom extends Atom {
final public static SpaceAtom hsep = new SpaceAtom(TeXConstants.UNIT_EM, 1f, 0.0f, 0.0f);
final public static SpaceAtom semihsep = new SpaceAtom(TeXConstants.UNIT_EM, 0.5f, 0.0f, 0.0f);
final public static SpaceAtom vsep_in = new SpaceAtom(TeXConstants.UNIT_EX, 0.0f, 1f, 0.0f);
final public static SpaceAtom vsep_ext_top = new SpaceAtom(TeXConstants.UNIT_EX, 0.0f, 0.4f, 0.0f);
final public static SpaceAtom vsep_ext_bot = new SpaceAtom(TeXConstants.UNIT_EX, 0.0f, 0.4f, 0.0f);
public static final int ARRAY = 0;
public static final int MATRIX = 1;
public static final int ALIGN = 2;
public static final int ALIGNAT = 3;
public static final int FLALIGN = 4;
public static final int SMALLMATRIX = 5;
public static final int ALIGNED = 6;
public static final int ALIGNEDAT = 7;
private static final Box nullBox = new StrutBox(0, 0, 0, 0);
private ArrayOfAtoms matrix;
private int[] position;
private Map<Integer, VlineAtom> vlines = new HashMap<Integer, VlineAtom>();
private boolean isPartial;
private boolean spaceAround;
private ArrayList<Rectangle2D> rectangles = new ArrayList<Rectangle2D>();
private ArrayList<Color> colors = new ArrayList<Color>();
final private static SpaceAtom align = new SpaceAtom(TeXConstants.MEDMUSKIP);
@Override
final public Atom duplicate() {
MatrixAtom ret = new MatrixAtom();
ret.matrix = matrix;
ret.position = position;
ret.vlines = vlines;
ret.isPartial = isPartial;
ret.spaceAround = spaceAround;
ret.rectangles = rectangles;
ret.colors = colors;
return setFields(ret);
}
/**
* Creates an empty matrix
*
*/
public MatrixAtom(boolean isPartial, ArrayOfAtoms array, String options, boolean spaceAround) {
this.isPartial = isPartial;
this.matrix = array;
this.type = ARRAY;
this.spaceAround = spaceAround;
parsePositions(new StringBuilder(options));
}
/**
* Creates an empty matrix
*
*/
public MatrixAtom(boolean isPartial, ArrayOfAtoms array, String options) {
this(isPartial, array, options, false);
}
/**
* Creates an empty matrix
*
*/
public MatrixAtom(ArrayOfAtoms array, String options) {
this(false, array, options);
}
public MatrixAtom(boolean isPartial, ArrayOfAtoms array, int type) {
this(isPartial, array, type, false);
}
public MatrixAtom(boolean isPartial, ArrayOfAtoms array, int type, boolean spaceAround) {
this.isPartial = isPartial;
this.matrix = array;
this.type = type;
this.spaceAround = spaceAround;
if (type != MATRIX && type != SMALLMATRIX) {
position = new int[matrix.col];
for (int i = 0; i < matrix.col; i += 2) {
position[i] = TeXConstants.ALIGN_RIGHT;
if (i + 1 < matrix.col) {
position[i + 1] = TeXConstants.ALIGN_LEFT;
}
}
} else {
position = new int[matrix.col];
for (int i = 0; i < matrix.col; i++) {
position[i] = TeXConstants.ALIGN_CENTER;
}
}
}
public MatrixAtom(boolean isPartial, ArrayOfAtoms array, int type, int alignment) {
this(isPartial, array, type, alignment, true);
}
public MatrixAtom(boolean isPartial, ArrayOfAtoms array, int type, int alignment, boolean spaceAround) {
this.isPartial = isPartial;
this.matrix = array;
this.type = type;
this.spaceAround = spaceAround;
position = new int[matrix.col];
for (int i = 0; i < matrix.col; i++) {
position[i] = alignment;
}
}
public MatrixAtom(ArrayOfAtoms array, int type) {
this(false, array, type);
}
private MatrixAtom() {
}
private void parsePositions(StringBuilder opt) {
int len = opt.length();
int pos = 0;
char ch;
TeXFormula tf;
TeXParser tp;
List<Integer> lposition = new ArrayList<Integer>();
while (pos < len) {
ch = opt.charAt(pos);
switch (ch) {
case 'l':
lposition.add(TeXConstants.ALIGN_LEFT);
break;
case 'r':
lposition.add(TeXConstants.ALIGN_RIGHT);
break;
case 'c':
lposition.add(TeXConstants.ALIGN_CENTER);
break;
case '|':
int nb = 1;
while (++pos < len) {
ch = opt.charAt(pos);
if (ch != '|') {
pos--;
break;
}
nb++;
}
vlines.put(lposition.size(), new VlineAtom(nb));
break;
case '@':
pos++;
tf = new TeXFormula();
tp = new TeXParser(isPartial, opt.substring(pos), tf, false);
Atom at = tp.getArgument();
matrix.col++;
for (int j = 0; j < matrix.row; j++) {
matrix.get(j).add(lposition.size(), at);
}
lposition.add(TeXConstants.ALIGN_NONE);
pos += tp.getPos();
pos--;
break;
case '*':
pos++;
tf = new TeXFormula();
tp = new TeXParser(isPartial, opt.substring(pos), tf, false);
String[] args = tp.getOptsArgs(2, 0);
pos += tp.getPos();
int nrep = Integer.parseInt(args[1]);
String str = "";
for (int j = 0; j < nrep; j++) {
str += args[2];
}
opt.insert(pos, str);
len = opt.length();
pos--;
break;
case ' ':
case '\t':
break;
default:
lposition.add(TeXConstants.ALIGN_CENTER);
}
pos++;
}
for (int j = lposition.size(); j < matrix.col; j++) {
lposition.add(TeXConstants.ALIGN_CENTER);
}
if (lposition.size() != 0) {
Integer[] tab = lposition.toArray(new Integer[0]);
position = new int[tab.length];
for (int i = 0; i < tab.length; i++) {
position[i] = tab[i];
}
} else {
position = new int[] { TeXConstants.ALIGN_CENTER };
}
}
public Box[] getColumnSep(TeXEnvironment env, double width) {
int col = matrix.col;
Box[] arr = new Box[col + 1];
Box Align, AlignSep, Hsep;
double h, w = env.getTextwidth();
int i;
if (type == ALIGNED || type == ALIGNEDAT) {
w = Double.POSITIVE_INFINITY;
}
switch (type) {
case ARRAY:
// Array : hsep_col/2 elem hsep_col elem hsep_col ... hsep_col elem hsep_col/2
i = 1;
if (position[0] == TeXConstants.ALIGN_NONE) {
arr[1] = new StrutBox(0.0f, 0.0f, 0.0f, 0.0f);
i = 2;
}
if (spaceAround) {
arr[0] = semihsep.createBox(env);
} else {
arr[0] = new StrutBox(0.0f, 0.0f, 0.0f, 0.0f);
}
arr[col] = arr[0];
Hsep = hsep.createBox(env);
for (; i < col; i++) {
if (position[i] == TeXConstants.ALIGN_NONE) {
arr[i] = new StrutBox(0.0f, 0.0f, 0.0f, 0.0f);
arr[i + 1] = arr[i];
i++;
} else {
arr[i] = Hsep;
}
}
return arr;
case MATRIX:
case SMALLMATRIX:
// Simple matrix : (hsep_col/2 or 0) elem hsep_col elem hsep_col ... hsep_col elem
// (hsep_col/2 or 0)
arr[0] = nullBox;
arr[col] = arr[0];
Hsep = hsep.createBox(env);
for (i = 1; i < col; i++) {
arr[i] = Hsep;
}
return arr;
case ALIGNED:
case ALIGN:
// Align env. : hsep=(textwidth-matWidth)/(2n+1) and hsep eq_lft \medskip el_rgt hsep
// ... hsep elem hsep
Align = align.createBox(env);
if (w != Double.POSITIVE_INFINITY) {
h = Math.max((w - width - (col / 2) * Align.getWidth()) / Math.floor((col + 3) / 2),
0);
AlignSep = new StrutBox(h, 0.0f, 0.0f, 0.0f);
} else {
AlignSep = hsep.createBox(env);
}
arr[col] = AlignSep;
for (i = 0; i < col; i++) {
if (i % 2 == 0) {
arr[i] = AlignSep;
} else {
arr[i] = Align;
}
}
break;
case ALIGNEDAT:
case ALIGNAT:
// Alignat env. : hsep=(textwidth-matWidth)/2 and hsep elem ... elem hsep
if (w != Double.POSITIVE_INFINITY) {
h = Math.max((w - width) / 2, 0);
} else {
h = 0;
}
Align = align.createBox(env);
Box empty = nullBox;
arr[0] = new StrutBox(h, 0.0f, 0.0f, 0.0f);
arr[col] = arr[0];
for (i = 1; i < col; i++) {
if (i % 2 == 0) {
arr[i] = empty;
} else {
arr[i] = Align;
}
}
break;
case FLALIGN:
// flalign env. : hsep=(textwidth-matWidth)/(2n+1) and hsep eq_lft \medskip el_rgt hsep
// ... hsep elem hsep
Align = align.createBox(env);
if (w != Double.POSITIVE_INFINITY) {
h = Math.max((w - width - (col / 2) * Align.getWidth()) / Math.floor((col - 1) / 2),
0);
AlignSep = new StrutBox(h, 0.0f, 0.0f, 0.0f);
} else {
AlignSep = hsep.createBox(env);
}
arr[0] = nullBox;
arr[col] = arr[0];
for (i = 1; i < col; i++) {
if (i % 2 == 0) {
arr[i] = AlignSep;
} else {
arr[i] = Align;
}
}
break;
}
if (w == Double.POSITIVE_INFINITY) {
arr[0] = nullBox;
arr[col] = arr[0];
}
return arr;
}
@Override
public TableBox createBox(TeXEnvironment env0) {
rectangles.clear();
colors.clear();
int row = matrix.row;
int col = matrix.col;
Box[][] boxarr = new Box[row][col];
double[] lineDepth = new double[row];
double[] lineHeight = new double[row];
double[] rowWidth = new double[col];
double matW = 0;
double drt = env0.getTeXFont().getDefaultRuleThickness(env0.getStyle());
TeXEnvironment env = env0;
if (type == SMALLMATRIX) {
env = env.copy();
env.setStyle(TeXConstants.STYLE_SCRIPT);
}
List<MulticolumnAtom> listMulti = new ArrayList<MulticolumnAtom>();
for (int i = 0; i < row; i++) {
lineDepth[i] = 0;
lineHeight[i] = 0;
for (int j = 0; j < col; j++) {
Atom at = null;
try {
at = matrix.get(i, j);
} catch (Exception e) {
// The previous atom was an intertext atom
// position[j - 1] = -1;
boxarr[i][j - 1].type = TeXConstants.TYPE_INTERTEXT;
j = col - 1;
}
boxarr[i][j] = (at == null) ? nullBox : at.createBox(env);
lineDepth[i] = Math.max(boxarr[i][j].getDepth(), lineDepth[i]);
lineHeight[i] = Math.max(boxarr[i][j].getHeight(), lineHeight[i]);
if (boxarr[i][j].type != TeXConstants.TYPE_MULTICOLUMN) {
rowWidth[j] = Math.max(boxarr[i][j].getWidth(), rowWidth[j]);
} else {
((MulticolumnAtom) at).setRowColumn(i, j);
listMulti.add((MulticolumnAtom) at);
}
}
}
for (int i = 0; i < listMulti.size(); i++) {
MulticolumnAtom multi = listMulti.get(i);
int c = multi.getCol();
int r = multi.getRow();
int n = multi.getSkipped();
double w = 0;
for (int j = c; j < c + n; j++) {
w += rowWidth[j];
}
if (boxarr[r][c].getWidth() > w) {
double extraW = (boxarr[r][c].getWidth() - w) / n;
for (int j = c; j < c + n; j++) {
rowWidth[j] += extraW;
}
}
}
for (int j = 0; j < col; j++) {
matW += rowWidth[j];
}
Box[] Hsep = getColumnSep(env, matW);
for (int j = 0; j < col + 1; j++) {
matW += Hsep[j].getWidth();
if (vlines.get(j) != null) {
matW += vlines.get(j).getWidth(env);
}
}
VerticalBox vb = new VerticalBox();
Box Vsep = vsep_in.createBox(env);
vb.add(vsep_ext_top.createBox(env));
double totalHeight = 0;
for (int i = 0; i < row; i++) {
HorizontalBox hb = new HorizontalBox();
for (int j = 0; j < col; j++) {
switch (boxarr[i][j].type) {
case -1:
case TeXConstants.TYPE_MULTICOLUMN:
if (j == 0) {
if (vlines.get(0) != null) {
VlineAtom vat = vlines.get(0);
vat.setHeight(lineHeight[i] + lineDepth[i] + Vsep.getHeight());
vat.setShift(lineDepth[i] + Vsep.getHeight() / 2);
Box vatBox = vat.createBox(env);
hb.add(new HorizontalBox(vatBox, Hsep[0].getWidth() + vatBox.getWidth(),
TeXConstants.ALIGN_LEFT));
} else {
hb.add(Hsep[0]);
}
}
boolean lastVline = true;
if (boxarr[i][j].type == -1) {
if (matrix.getColor(i, j) != null) {
colors.add(matrix.getColor(i, j));
double sepWidth = j == 0 ? 2 * Hsep[j].getWidth()
: Hsep[j].getWidth();
double thickness = env.getTeXFont()
.getDefaultRuleThickness(env.getStyle());
rectangles.add(FactoryProvider.getInstance()
.getGeomFactory()
.createRectangle2D(
hb.getWidth()
- sepWidth / 2,
vb.getHeight() + vb.getDepth()
- Vsep.getHeight()
- thickness / 2,
rowWidth[j] + sepWidth + thickness,
lineHeight[i] + lineDepth[i]
+ Vsep.getHeight()));
}
hb.add(new HorizontalBox(boxarr[i][j], rowWidth[j], position[j]));
} else {
Box b = generateMulticolumn(env, Hsep, rowWidth, i, j);
MulticolumnAtom matom = (MulticolumnAtom) matrix.get(i,
j);
j += matom.getSkipped() - 1;
hb.add(b);
lastVline = matom.hasRightVline();
}
if (lastVline && vlines.get(j + 1) != null) {
VlineAtom vat = vlines.get(j + 1);
vat.setHeight(lineHeight[i] + lineDepth[i] + Vsep.getHeight());
vat.setShift(lineDepth[i] + Vsep.getHeight() / 2);
Box vatBox = vat.createBox(env);
if (j < col - 1) {
hb.add(new HorizontalBox(vatBox, Hsep[j + 1].getWidth() + vatBox.getWidth(),
TeXConstants.ALIGN_CENTER));
} else {
hb.add(new HorizontalBox(vatBox, Hsep[j + 1].getWidth() + vatBox.getWidth(),
TeXConstants.ALIGN_RIGHT));
}
} else {
hb.add(Hsep[j + 1]);
}
break;
case TeXConstants.TYPE_INTERTEXT:
double f = env.getTextwidth();
f = f == Double.POSITIVE_INFINITY ? rowWidth[j] : f;
hb = new HorizontalBox(boxarr[i][j], f, TeXConstants.ALIGN_LEFT);
j = col - 1;
break;
case TeXConstants.TYPE_HLINE:
HlineAtom at = (HlineAtom) matrix.get(i, j);
at.setWidth(matW);
if (i >= 1 && matrix.get(i - 1, j) instanceof HlineAtom) {
hb.add(new StrutBox(0, 2 * drt, 0, 0));
at.setShift(-Vsep.getHeight() / 2 + drt);
} else {
at.setShift(-Vsep.getHeight() / 2);
}
hb.add(at.createBox(env));
j = col;
break;
}
}
if (boxarr[i][0].type != TeXConstants.TYPE_HLINE) {
hb.setHeight(lineHeight[i]);
hb.setDepth(lineDepth[i]);
vb.add(hb);
if (i < row - 1) {
vb.add(Vsep);
}
} else {
vb.add(hb);
}
}
vb.add(vsep_ext_bot.createBox(env));
totalHeight = vb.getHeight() + vb.getDepth();
double axis = env.getTeXFont().getAxisHeight(env.getStyle());
vb.setHeight(totalHeight / 2 + axis);
vb.setDepth(totalHeight / 2 - axis);
return new TableBox(vb, rectangles, colors);
}
private Box generateMulticolumn(TeXEnvironment env, Box[] Hsep, double[] rowWidth, int i, int j) {
double w = 0;
MulticolumnAtom mca = (MulticolumnAtom) matrix.get(i, j);
int k, n = mca.getSkipped();
for (k = j; k < j + n - 1; k++) {
w += rowWidth[k] + Hsep[k + 1].getWidth();
if (vlines.get(k + 1) != null) {
w += vlines.get(k + 1).getWidth(env);
}
}
w += rowWidth[k];
Box b = mca.createBox(env);
double bw = b.getWidth();
if (bw > w) {
// It isn't a good idea but for the moment I have no other solution !
w = 0;
}
mca.setWidth(w);
b = mca.createBox(env);
return b;
}
}