/*******************************************************************************
* Copyright (c) 2004, 2008 John Krasnay and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* John Krasnay - initial API and implementation
*******************************************************************************/
package net.sf.vex.layout;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.sf.vex.core.Caret;
import net.sf.vex.core.IntRange;
import net.sf.vex.css.CSS;
import net.sf.vex.css.Styles;
import net.sf.vex.dom.Element;
/**
* A box that wraps inline content into a paragraph.
*/
public class ParagraphBox extends AbstractBox implements BlockBox {
private LineBox[] children;
private LineBox firstContentLine;
private LineBox lastContentLine;
/**
* Class constructor.
*
* @param children Line boxes that comprise the paragraph.
*/
private ParagraphBox(LineBox[] children) {
this.children = children;
for (int i = 0; i < children.length; i++) {
if (children[i].hasContent()) {
if (this.firstContentLine == null) {
this.firstContentLine = children[i];
}
this.lastContentLine = children[i];
}
}
}
/**
* Create a paragraph by word-wrapping a list of inline boxes.
* @param context LayoutContext used for this layout.
* @param element Element that controls the styling for this paragraph.
* @param inlines List of InlineBox objects to be wrapped
* @param width width to which the paragraph is to be wrapped
* @return
*/
public static ParagraphBox create(LayoutContext context, Element element, List inlines, int width) {
InlineBox[] array = (InlineBox[]) inlines.toArray(new InlineBox[inlines.size()]);
return create(context, element, array, width);
}
/**
* Create a paragraph by word-wrapping a list of inline boxes.
* @param context LayoutContext used for this layout
* @param element Element that controls the styling of this paragraph,
* in particular text alignment.
* @param inlines Array of InlineBox objects to be wrapped.
* @param width width to which the paragraph is to be wrapped.
*/
public static ParagraphBox create(LayoutContext context, Element element, InlineBox[] inlines, int width) {
// lines is the list of LineBoxes we are creating
List lines = new ArrayList();
InlineBox right = new LineBox(context, element, inlines);
while (right != null) {
InlineBox.Pair pair = right.split(context, width, true);
lines.add(pair.getLeft());
right = pair.getRight();
}
Styles styles = context.getStyleSheet().getStyles(element);
String textAlign = styles.getTextAlign();
// y-offset of the next line
int y = 0;
int actualWidth = 0;
for (Iterator it = lines.iterator(); it.hasNext(); ) {
LineBox lineBox = (LineBox) it.next();
int x;
if (textAlign.equals(CSS.RIGHT)) {
x = width - lineBox.getWidth();
} else if (textAlign.equals(CSS.CENTER)) {
x = (width - lineBox.getWidth()) / 2;
} else {
x = 0;
}
lineBox.setX(x);
lineBox.setY(y);
y += lineBox.getHeight();
actualWidth = Math.max(actualWidth, lineBox.getWidth());
}
LineBox[] children = (LineBox[]) lines.toArray(new LineBox[lines.size()]);
ParagraphBox para = new ParagraphBox(children);
para.setWidth(actualWidth);
para.setHeight(y);
// BlockElementBox uses a scaling factor to estimate box height based
// on font size, layout width, and character count, as follows.
//
// estHeight = factor * fontSize * fontSize * charCount / width
//
// This bit reports the actual factor that would correctly estimate
// the height of a BlockElementBox containing only this paragraph.
//
// factor = estHeight * width / (fontSize * fontSize * charCount)
//
/*
Box firstContentBox = null;
for (int i = 0; i < inlines.length; i++) {
Box box = inlines[i];
if (box.hasContent()) {
firstContentBox = box;
break;
}
}
if (firstContentBox != null) {
float fontSize = styles.getFontSize();
int charCount = lastContentBox.getEndOffset() - firstContentBox.getStartOffset();
float factor = para.getHeight() * para.getWidth() / (fontSize * fontSize * charCount);
System.out.println("Actual factor is " + factor);
}
*/
return para;
}
/**
* @see net.sf.vex.layout.Box#getCaret(net.sf.vex.layout.LayoutContext, int)
*/
public Caret getCaret(LayoutContext context, int offset) {
LineBox line = this.getLineAt(offset);
Caret caret = line.getCaret(context, offset);
caret.translate(line.getX(), line.getY());
return caret;
}
public Box[] getChildren() {
return this.children;
}
public int getEndOffset() {
return this.lastContentLine.getEndOffset();
}
/**
* @see net.sf.vex.layout.BlockBox#getFirstLine()
*/
public LineBox getFirstLine() {
if (this.children.length == 0) {
return null;
} else {
return this.children[0];
}
}
/**
* @see net.sf.vex.layout.BlockBox#getLastLine()
*/
public LineBox getLastLine() {
if (this.children.length == 0) {
return null;
} else {
return this.children[this.children.length - 1];
}
}
/**
* Returns the LineBox at the given offset.
* @param offset the offset to check.
*/
public LineBox getLineAt(int offset) {
LineBox[] children = this.children;
for (int i = 0; i < children.length; i++) {
if (children[i].hasContent() && offset <= children[i].getEndOffset()) {
return children[i];
}
}
return this.lastContentLine;
}
public int getLineEndOffset(int offset) {
return this.getLineAt(offset).getEndOffset();
}
public int getLineStartOffset(int offset) {
return this.getLineAt(offset).getStartOffset();
}
public int getMarginBottom() {
return 0;
}
public int getMarginTop() {
return 0;
}
public int getNextLineOffset(LayoutContext context, int offset, int x) {
LineBox nextLine = null;
LineBox[] children = this.children;
for (int i = 0; i < children.length; i++) {
if (children[i].hasContent() && children[i].getStartOffset() > offset) {
nextLine = children[i];
break;
}
}
if (nextLine == null) {
//return this.getEndOffset() + 1;
return -1;
} else {
return nextLine.viewToModel(context, x - nextLine.getX(), 0);
}
}
public BlockBox getParent() {
throw new IllegalStateException("ParagraphBox does not currently track parent");
}
public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
LineBox prevLine = null;
LineBox[] children = this.children;
for (int i = children.length - 1; i >= 0; i--) {
if (children[i].hasContent() && children[i].getEndOffset() < offset) {
prevLine = children[i];
break;
}
}
if (prevLine == null) {
//return this.getStartOffset() - 1;
return -1;
} else {
return prevLine.viewToModel(context, x - prevLine.getX(), 0);
}
}
public int getStartOffset() {
return this.firstContentLine.getStartOffset();
}
public boolean hasContent() {
return this.firstContentLine != null;
}
public IntRange layout(LayoutContext context, int top, int bottom) {
return null;
}
public void invalidate(boolean direct) {
throw new IllegalStateException("invalidate called on a non-element BlockBox");
}
public void setInitialSize(LayoutContext context) {
// NOP - size calculated in factory method
}
public String toString() {
return "ParagraphBox";
}
public int viewToModel(LayoutContext context, int x, int y) {
LineBox[] children = this.children;
for (int i = 0; i < children.length; i++) {
Box child = children[i];
if (child.hasContent() && y <= child.getY() + child.getHeight()) {
return child.viewToModel(context, x - child.getX(), y - child.getY());
}
}
throw new RuntimeException("No line at (" + x + ", " + y + ")");
}
//===================================================== PRIVATE
}