org.xhtmlrenderer.layout.FloatManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-renderer Show documentation
Show all versions of core-renderer Show documentation
An XML/XHTML CSS 2.1 Renderer library in pure Java for rendering to PDF, images, and Swing panels.
The newest version!
/*
* {{{ header & license
* Copyright (c) 2005 Wisconsin Court System
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* }}}
*/
package org.xhtmlrenderer.layout;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.xhtmlrenderer.css.style.CssContext;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.render.Box;
import org.xhtmlrenderer.render.LineBox;
/**
* A class that manages all floated boxes in a given block formatting context.
* It is responsible for positioning floats and calculating clearance for
* non-floated (block) boxes.
*/
public class FloatManager {
private static final int LEFT = 1;
private static final int RIGHT = 2;
private List _leftFloats = new ArrayList();
private List _rightFloats = new ArrayList();
private Box _master;
public void floatBox(LayoutContext c, Layer layer, BlockFormattingContext bfc, BlockBox box) {
if (box.getStyle().isFloatedLeft()) {
position(c, bfc, box, LEFT);
save(box, layer, bfc, LEFT);
} else if (box.getStyle().isFloatedRight()) {
position(c, bfc, box, RIGHT);
save(box, layer, bfc, RIGHT);
}
}
public void clear(CssContext cssCtx, BlockFormattingContext bfc, Box box) {
if (box.getStyle().isClearLeft()) {
moveClear(cssCtx, bfc, box, getFloats(LEFT));
}
if (box.getStyle().isClearRight()) {
moveClear(cssCtx, bfc, box, getFloats(RIGHT));
}
}
private void save(BlockBox current, Layer layer, BlockFormattingContext bfc, int direction) {
Point p = bfc.getOffset();
getFloats(direction).add(new BoxOffset(current, p.x, p.y));
layer.addFloat(current, bfc);
current.getFloatedBoxData().setManager(this);
current.calcCanvasLocation();
current.calcChildLocations();
}
private void position(CssContext cssCtx, BlockFormattingContext bfc,
BlockBox current, int direction) {
moveAllTheWayOver(current, direction);
alignToLastOpposingFloat(cssCtx, bfc, current, direction);
alignToLastFloat(cssCtx, bfc, current, direction);
if (!fitsInContainingBlock(current) ||
overlaps(cssCtx, bfc, current, getFloats(direction))) {
moveAllTheWayOver(current, direction);
moveFloatBelow(cssCtx, bfc, current, getFloats(direction));
}
if (overlaps(cssCtx, bfc, current, getOpposingFloats(direction))) {
moveAllTheWayOver(current, direction);
moveFloatBelow(cssCtx, bfc, current, getFloats(direction));
moveFloatBelow(cssCtx, bfc, current, getOpposingFloats(direction));
}
if (current.getStyle().isCleared()) {
if (current.getStyle().isClearLeft() && direction == LEFT) {
moveAllTheWayOver(current, LEFT);
} else if (current.getStyle().isClearRight() && direction == RIGHT) {
moveAllTheWayOver(current, RIGHT);
}
moveFloatBelow(cssCtx, bfc, current, getFloats(direction));
}
}
private List getFloats(int direction) {
return direction == LEFT ? _leftFloats : _rightFloats;
}
private List getOpposingFloats(int direction) {
return direction == LEFT ? _rightFloats : _leftFloats;
}
private void alignToLastFloat(CssContext cssCtx,
BlockFormattingContext bfc, BlockBox current, int direction) {
List floats = getFloats(direction);
if (floats.size() > 0) {
Point offset = bfc.getOffset();
BoxOffset lastOffset = (BoxOffset) floats.get(floats.size() - 1);
BlockBox last = lastOffset.getBox();
Rectangle currentBounds = current.getMarginEdge(cssCtx, -offset.x, -offset.y);
Rectangle lastBounds = last.getMarginEdge(cssCtx, -lastOffset.getX(), -lastOffset.getY());
boolean moveOver = false;
if (currentBounds.y < lastBounds.y) {
currentBounds.translate(0, lastBounds.y - currentBounds.y);
moveOver = true;
}
if (currentBounds.y >= lastBounds.y && currentBounds.y < lastBounds.y + lastBounds.height) {
moveOver = true;
}
if (moveOver) {
if (direction == LEFT) {
currentBounds.x = lastBounds.x + last.getWidth();
} else if (direction == RIGHT) {
currentBounds.x = lastBounds.x - current.getWidth();
}
currentBounds.translate(offset.x, offset.y);
current.setX(currentBounds.x);
current.setY(currentBounds.y);
}
}
}
private void alignToLastOpposingFloat(CssContext cssCtx,
BlockFormattingContext bfc, BlockBox current, int direction) {
List floats = getOpposingFloats(direction);
if (floats.size() > 0) {
Point offset = bfc.getOffset();
BoxOffset lastOffset = (BoxOffset) floats.get(floats.size() - 1);
Rectangle currentBounds = current.getMarginEdge(cssCtx, -offset.x, -offset.y);
Rectangle lastBounds = lastOffset.getBox().getMarginEdge(cssCtx,
-lastOffset.getX(), -lastOffset.getY());
if (currentBounds.y < lastBounds.y) {
currentBounds.translate(0, lastBounds.y - currentBounds.y);
currentBounds.translate(offset.x, offset.y);
current.setY(currentBounds.y);
}
}
}
private void moveAllTheWayOver(BlockBox current, int direction) {
if (direction == LEFT) {
current.setX(0);
} else if (direction == RIGHT) {
current.setX(current.getContainingBlock().getContentWidth() - current.getWidth());
}
}
private boolean fitsInContainingBlock(BlockBox current) {
return current.getX() >= 0 &&
(current.getX() + current.getWidth()) <= current.getContainingBlock().getContentWidth();
}
private int findLowestY(CssContext cssCtx, List floats) {
int result = 0;
for (Iterator i = floats.iterator(); i.hasNext();) {
BoxOffset floater = (BoxOffset) i.next();
Rectangle bounds = floater.getBox().getMarginEdge(
cssCtx, -floater.getX(), -floater.getY());
if (bounds.y + bounds.height > result) {
result = bounds.y + bounds.height;
}
}
return result;
}
public int getClearDelta(CssContext cssCtx, int bfcRelativeY) {
int lowestLeftY = findLowestY(cssCtx, getFloats(LEFT));
int lowestRightY = findLowestY(cssCtx, getFloats(RIGHT));
int lowestY = Math.max(lowestLeftY, lowestRightY);
return lowestY - bfcRelativeY;
}
private boolean overlaps(CssContext cssCtx, BlockFormattingContext bfc,
BlockBox current, List floats) {
Point offset = bfc.getOffset();
Rectangle bounds = current.getMarginEdge(cssCtx, -offset.x, -offset.y);
for (Iterator i = floats.iterator(); i.hasNext();) {
BoxOffset floater = (BoxOffset) i.next();
Rectangle floaterBounds = floater.getBox().getMarginEdge(cssCtx,
-floater.getX(), -floater.getY());
if (floaterBounds.intersects(bounds)) {
return true;
}
}
return false;
}
private void moveFloatBelow(CssContext cssCtx, BlockFormattingContext bfc,
Box current, List floats) {
if (floats.size() == 0) {
return;
}
Point offset = bfc.getOffset();
int boxY = current.getY() - offset.y;
int floatY = findLowestY(cssCtx, floats);
if (floatY - boxY > 0) {
current.setY(current.getY() + (floatY - boxY));
}
}
private void moveClear(CssContext cssCtx, BlockFormattingContext bfc,
Box current, List floats) {
if (floats.size() == 0) {
return;
}
// Translate from box coords to BFC coords
Point offset = bfc.getOffset();
Rectangle bounds = current.getBorderEdge(
current.getX()-offset.x, current.getY()-offset.y, cssCtx);
int y = findLowestY(cssCtx, floats);
if (bounds.y < y) {
// Translate bottom margin edge of lowest float back to box coords
// and set the box's border edge to that value
bounds.y = y;
bounds.translate(offset.x, offset.y);
current.setY(bounds.y - (int)current.getMargin(cssCtx).top());
}
}
public void removeFloat(BlockBox floater) {
removeFloat(floater, getFloats(LEFT));
removeFloat(floater, getFloats(RIGHT));
}
private void removeFloat(BlockBox floater, List floats) {
for (Iterator i = floats.iterator(); i.hasNext();) {
BoxOffset boxOffset = (BoxOffset) i.next();
if (boxOffset.getBox().equals(floater)) {
i.remove();
floater.getFloatedBoxData().setManager(null);
}
}
}
public void calcFloatLocations() {
calcFloatLocations(getFloats(LEFT));
calcFloatLocations(getFloats(RIGHT));
}
private void calcFloatLocations(List floats) {
for (Iterator i = floats.iterator(); i.hasNext();) {
BoxOffset boxOffset = (BoxOffset) i.next();
boxOffset.getBox().calcCanvasLocation();
boxOffset.getBox().calcChildLocations();
}
}
private void applyLineHeightHack(CssContext cssCtx, Box line, Rectangle bounds) {
// this is a hack to deal with lines w/o width or height. is this valid?
// possibly, since the line doesn't know how long it should be until it's already
// done float adjustments
if (line.getHeight() == 0) {
bounds.height = (int)line.getStyle().getLineHeight(cssCtx);
}
}
public int getNextLineBoxDelta(CssContext cssCtx, BlockFormattingContext bfc,
LineBox line, int containingBlockContentWidth) {
BoxDistance left = getFloatDistance(cssCtx, bfc, line, containingBlockContentWidth, _leftFloats, LEFT);
BoxDistance right = getFloatDistance(cssCtx, bfc, line, containingBlockContentWidth, _rightFloats, RIGHT);
int leftDelta;
int rightDelta;
if (left.getBox() != null) {
leftDelta = calcDelta(cssCtx, line, left);
} else {
leftDelta = 0;
}
if (right.getBox() != null) {
rightDelta = calcDelta(cssCtx, line, right);
} else {
rightDelta = 0;
}
return Math.max(leftDelta, rightDelta);
}
private int calcDelta(CssContext cssCtx, LineBox line, BoxDistance boxDistance) {
BlockBox floated = boxDistance.getBox();
Rectangle rect = floated.getBorderEdge(floated.getAbsX(), floated.getAbsY(), cssCtx);
int bottom = rect.y + rect.height;
return bottom - line.getAbsY();
}
public int getLeftFloatDistance(CssContext cssCtx, BlockFormattingContext bfc,
LineBox line, int containingBlockContentWidth) {
return getFloatDistance(cssCtx, bfc, line, containingBlockContentWidth, _leftFloats, LEFT).getDistance();
}
public int getRightFloatDistance(CssContext cssCtx, BlockFormattingContext bfc,
LineBox line, int containingBlockContentWidth) {
return getFloatDistance(cssCtx, bfc, line, containingBlockContentWidth, _rightFloats, RIGHT).getDistance();
}
private BoxDistance getFloatDistance(CssContext cssCtx, BlockFormattingContext bfc,
LineBox line, int containingBlockContentWidth,
List floatsList, int direction) {
if (floatsList.size() == 0) {
return new BoxDistance(null, 0);
}
Point offset = bfc.getOffset();
Rectangle lineBounds = line.getMarginEdge(cssCtx, -offset.x, -offset.y);
lineBounds.width = containingBlockContentWidth;
int farthestOver = direction == LEFT ? lineBounds.x : lineBounds.x + lineBounds.width;
applyLineHeightHack(cssCtx, line, lineBounds);
BlockBox farthestOverBox = null;
for (int i = 0; i < floatsList.size(); i++) {
BoxOffset floater = (BoxOffset) floatsList.get(i);
Rectangle fr = floater.getBox().getMarginEdge(cssCtx, -floater.getX(), -floater.getY());
if (lineBounds.intersects(fr)) {
if (direction == LEFT && fr.x + fr.width > farthestOver) {
farthestOver = fr.x + fr.width;
} else if (direction == RIGHT && fr.x < farthestOver) {
farthestOver = fr.x;
}
farthestOverBox = floater.getBox();
}
}
if (direction == LEFT) {
return new BoxDistance(farthestOverBox, farthestOver - lineBounds.x);
} else {
return new BoxDistance(farthestOverBox,lineBounds.x + lineBounds.width - farthestOver);
}
}
public void setMaster(Box owner) {
_master = owner;
}
public Box getMaster() {
return _master;
}
public Point getOffset(BlockBox floater) {
// FIXME inefficient (but probably doesn't matter)
return getOffset(floater,
floater.getStyle().isFloatedLeft() ? getFloats(LEFT) : getFloats(RIGHT));
}
private Point getOffset(BlockBox floater, List floats) {
for (Iterator i = floats.iterator(); i.hasNext();) {
BoxOffset boxOffset = (BoxOffset) i.next();
BlockBox box = boxOffset.getBox();
if (box.equals(floater)) {
return new Point(boxOffset.getX(), boxOffset.getY());
}
}
return null;
}
private void performFloatOperation(FloatOperation op, List floats) {
for (Iterator i = floats.iterator(); i.hasNext();) {
BoxOffset boxOffset = (BoxOffset) i.next();
BlockBox box = boxOffset.getBox();
box.setAbsX(box.getX() + getMaster().getAbsX() - boxOffset.getX());
box.setAbsY(box.getY() + getMaster().getAbsY() - boxOffset.getY());
op.operate(box);
}
}
public void performFloatOperation(FloatOperation op) {
performFloatOperation(op, getFloats(LEFT));
performFloatOperation(op, getFloats(RIGHT));
}
private static class BoxOffset {
private BlockBox _box;
private int _x;
private int _y;
public BoxOffset(BlockBox box, int x, int y) {
_box = box;
_x = x;
_y = y;
}
public BlockBox getBox() {
return _box;
}
public int getX() {
return _x;
}
public int getY() {
return _y;
}
}
private static class BoxDistance {
private BlockBox _box;
private int _distance;
public BoxDistance(BlockBox box, int distance) {
_box = box;
_distance = distance;
}
BlockBox getBox() {
return _box;
}
int getDistance() {
return _distance;
}
}
public interface FloatOperation {
public void operate(Box floater);
}
}
/*
* $Id$
*/