/* Box.java
* =========================================================================
* This file is originally part of the JMathTeX Library - http://jmathtex.sourceforge.net
*
* Copyright (C) 2004-2007 Universiteit Gent
* 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.
*
*/
/* Modified by Calixte Denizet */
package com.himamis.retex.renderer.share;
import java.util.ArrayList;
import java.util.LinkedList;
import com.himamis.retex.renderer.share.platform.Geom;
import com.himamis.retex.renderer.share.platform.Graphics;
import com.himamis.retex.renderer.share.platform.graphics.BasicStroke;
import com.himamis.retex.renderer.share.platform.graphics.Color;
import com.himamis.retex.renderer.share.platform.graphics.Graphics2DInterface;
import com.himamis.retex.renderer.share.platform.graphics.Stroke;
/**
* An abstract graphical representation of a formula, that can be painted. All characters, font
* sizes, positions are fixed. Only special Glue boxes could possibly stretch or shrink. A box has 3
* dimensions (width, height and depth), can be composed of other child boxes that can possibly be
* shifted (up, down, left or right). Child boxes can also be positioned outside their parent's box
* (defined by it's dimensions).
* <p>
* Subclasses must implement the abstract {@link #draw(Graphics2DInterface, float, float)} method
* (that paints the box). <b> This implementation must start with calling the method
* {@link #startDraw(Graphics2DInterface, float, float)} and end with calling the method
* {@link #endDraw(Graphics2DInterface)} to set and restore the color's that must be used for
* painting the box and to draw the background!</b> They must also implement the abstract
* {@link #getLastFontId()} method (the last font that will be used when this box will be painted).
*/
public abstract class Box {
final public static boolean DEBUG = false;
/**
* Factory providing platform independent implementations of forms used for drawing.
*/
protected final Geom geom;
/**
* Factory providing platform independent implementations of graphics related objects.
*/
protected final Graphics graphics;
/**
* The foreground color of the whole box. Child boxes can override this color. If it's null and
* it has a parent box, the foreground color of the parent will be used. If it has no parent,
* the foreground color of the component on which it will be painted, will be used.
*/
protected Color foreground;
/**
* The background color of the whole box. Child boxes can paint a background on top of this
* background. If it's null, no background will be painted.
*/
protected Color background;
private Color prevColor; // used temporarily in startDraw and endDraw
/**
* The width of this box, i.e. the value that will be used for further calculations.
*/
protected double width = 0;
/**
* The height of this box, i.e. the value that will be used for further calculations.
*/
protected double height = 0;
/**
* The depth of this box, i.e. the value that will be used for further calculations.
*/
protected double depth = 0;
/**
* The shift amount: the meaning depends on the particular kind of box (up, down, left, right)
*/
protected double shift = 0;
protected int type = -1;
/**
* List of child boxes
*/
protected LinkedList<Box> children = new LinkedList<Box>();
protected Color markForDEBUG;
private Atom atom;
/**
* Inserts the given box at the end of the list of child boxes.
*
* @param b the box to be inserted
*/
public void add(Box b) {
children.add(b);
}
/**
* Inserts the given box at the given position in the list of child boxes.
*
* @param pos the position at which to insert the given box
* @param b the box to be inserted
*/
public void add(int pos, Box b) {
children.add(pos, b);
}
/**
* Creates an empty box (no children) with all dimensions set to 0 and no foreground and
* background color set (default values will be used: null)
*/
protected Box() {
this(null, null);
}
/**
* Creates an empty box (no children) with all dimensions set to 0 and sets the foreground and
* background color of the box.
*
* @param fg the foreground color
* @param bg the background color
*/
protected Box(Color fg, Color bg) {
foreground = fg;
background = bg;
geom = new Geom();
graphics = new Graphics();
}
/**
* Get the width of this box.
*
* @return the width of this box
*/
public double getWidth() {
return width;
}
public void negWidth() {
width = -width;
}
/**
* Get the height of this box.
*
* @return the height of this box
*/
public double getHeight() {
return height;
}
/**
* Get the depth of this box.
*
* @return the depth of this box
*/
public double getDepth() {
return depth;
}
/**
* Get the shift amount for this box.
*
* @return the shift amount
*/
public double getShift() {
return shift;
}
/**
* Set the width for this box.
*
* @param w the width
*/
public void setWidth(double w) {
width = w;
}
/**
* Set the depth for this box.
*
* @param d the depth
*/
public void setDepth(double d) {
depth = d;
}
/**
* Set the height for this box.
*
* @param h the height
*/
public void setHeight(double h) {
height = h;
}
/**
* Set the shift amount for this box.
*
* @param s the shift amount
*/
public void setShift(double s) {
shift = s;
}
/**
* Paints this box at the given coordinates using the given graphics context.
*
* @param g2 the graphics (2D) context to use for painting
* @param x the x-coordinate
* @param y the y-coordinate
*/
public abstract void draw(Graphics2DInterface g2, double x, double y);
/**
* Get the id of the font that will be used the last when this box will be painted.
*
* @return the id of the last font that will be used.
*/
public abstract int getLastFontId();
/**
* Stores the old color setting, draws the background of the box (if not null) and sets the
* foreground color (if not null).
*
* @param g2 the graphics (2D) context
* @param x the x-coordinate
* @param y the y-coordinate
*/
protected void startDraw(Graphics2DInterface g2, double x, double y) {
// old color
prevColor = g2.getColor();
if (background != null) { // draw background
g2.setColor(background);
// was commented out https://jira.geogebra.org/browse/TRAC-4421
g2.fill(geom.createRectangle2D(x, y - height, width, height + depth));
}
if (foreground == null) {
g2.setColor(prevColor); // old foreground color
} else {
g2.setColor(foreground); // overriding foreground color
}
drawDebug(g2, x, y);
}
protected void drawDebug(Graphics2DInterface g2, double x, double y,
boolean showDepth) {
if (DEBUG) {
double x1 = x;
Stroke st = g2.getStroke();
if (markForDEBUG != null) {
Color c = g2.getColor();
g2.setColor(markForDEBUG);
g2.fill(geom.createRectangle2D(x1, y - height, width,
height + depth));
g2.setColor(c);
}
g2.setStroke(graphics.createBasicStroke(
(Math.abs(1 / g2.getTransform().getScaleX())), BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER));
if (width < 0) {
x1 += width;
width = -width;
}
g2.draw(geom.createRectangle2D(x1, y - height, width,
height + depth));
if (showDepth) {
Color c = g2.getColor();
g2.setColor(ColorUtil.RED);
if (depth > 0) {
g2.fill(geom.createRectangle2D(x1, y, width, depth));
g2.setColor(c);
g2.draw(geom.createRectangle2D(x1, y, width, depth));
} else if (depth < 0) {
g2.fill(geom.createRectangle2D(x1, y + depth, width,
-depth));
g2.setColor(c);
g2.draw(geom.createRectangle2D(x1, y + depth, width,
-depth));
} else {
g2.setColor(c);
}
}
g2.setStroke(st);
}
}
protected void drawDebug(Graphics2DInterface g2, double x, double y) {
if (DEBUG) {
drawDebug(g2, x, y, true);
}
}
/**
* Restores the previous color setting.
*
* @param g2 the graphics (2D) context
*/
protected void endDraw(Graphics2DInterface g2) {
g2.setColor(prevColor);
}
public void getPath(double x, double y, ArrayList<Integer> list) {
list.add(0);
if (children.size() > 0) {
children.get(0).getPath(x, y, list);
}
}
public boolean getSelectedPath(ArrayList<Integer> list, int depth) {
for (int idx = 0; idx < children.size(); idx++) {
if (children.get(idx).getSelectedPath(list, depth + 1)) {
list.add(idx);
return true;
}
}
// System.out.println(this + " BOX " + this.foreground);
if (this instanceof CursorBox) {
return true;
}
return false;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
append(sb, 0);
return sb.toString();
}
private void append(StringBuilder sb, int offset) {
for (int i = 0; i < offset; i++) {
sb.append(" ");
}
if (this instanceof CharBox) {
sb.append(toString());
} else {
sb.append(getClass().getSimpleName().replace("Box", ""));
}
sb.append("\n");
for (int i = 0; i < children.size(); i++) {
children.get(i).append(sb, offset + 1);
}
}
public Box getChild(int i) {
return children.get(i);
}
public Integer getCount() {
return children.size();
}
public Atom getAtom() {
return atom;
}
public Box setAtom(Atom parent) {
atom = parent;
return this;
}
}