/*
* Copyright (c) 2007 Matthew Hall 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:
* Matthew Hall - initial API and implementation
*/
package org.eclipse.nebula.paperclips.core.text;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.nebula.paperclips.core.CompositeEntry;
import org.eclipse.nebula.paperclips.core.CompositePiece;
import org.eclipse.nebula.paperclips.core.PaperClips;
import org.eclipse.nebula.paperclips.core.Print;
import org.eclipse.nebula.paperclips.core.PrintIterator;
import org.eclipse.nebula.paperclips.core.PrintPiece;
import org.eclipse.nebula.paperclips.core.internal.util.PrintSizeStrategy;
import org.eclipse.nebula.paperclips.core.internal.util.Util;
import org.eclipse.nebula.paperclips.core.text.internal.TextPrintPiece;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
/**
* A class for printing styled text. Text of varying size and style are aligned
* along the baseline.
*
* @author Matthew Hall
*/
public class StyledTextPrint implements Print {
private final List elements = new ArrayList();
private TextStyle style = new TextStyle();
/**
* Constructs a new StyledTextPrint.
*/
public StyledTextPrint() {
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((elements == null) ? 0 : elements.hashCode());
result = prime * result + ((style == null) ? 0 : style.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StyledTextPrint other = (StyledTextPrint) obj;
if (elements == null) {
if (other.elements != null)
return false;
} else if (!elements.equals(other.elements))
return false;
if (style == null) {
if (other.style != null)
return false;
} else if (!style.equals(other.style))
return false;
return true;
}
/**
* Sets the text style that will be applied to text added through the
* {@link #append(String)}
*
* @param style
* the new text style.
* @return this StyledTextPrint, for chaining method calls.
*/
public StyledTextPrint setStyle(TextStyle style) {
Util.notNull(style);
this.style = style;
return this;
}
/**
* Appends the given text to the end of the document, using the default
* style. This method is equivalent to calling append(text, getStyle()).
*
* @param text
* the text to append.
* @return this StyledTextPrint, for chaining method calls.
*/
public StyledTextPrint append(String text) {
return append(text, style);
}
/**
* Appends the given text to the end of the document, using the given style.
*
* @param text
* the text to append.
* @param style
* the text style.
* @return this StyledTextPrint, for chaining method calls.
*/
public StyledTextPrint append(String text, TextStyle style) {
TextPrint textPrint = new TextPrint(text, style);
textPrint.setWordSplitting(false);
return append(textPrint);
}
/**
* Appends a line break to the document. If a line break produces a blank
* line, that line will take the height of the font in the default text
* style.
*
* @return this StyledTextPrint, for chaining method calls.
*/
public StyledTextPrint newline() {
return append(new LineBreakPrint(style.getFontData()));
}
/**
* Appends the given element to the document.
*
* @param element
* the element to append.
* @return this StyledTextPrint, for chaining method calls.
*/
public StyledTextPrint append(Print element) {
elements.add(element);
return this;
}
public PrintIterator iterator(Device device, GC gc) {
return new StyledTextIterator((Print[]) elements
.toArray(new Print[elements.size()]), device, gc);
}
}
class StyledTextIterator implements PrintIterator {
private final PrintIterator[] elements;
private final Point minimumSize;
private final Point preferredSize;
private int cursor = 0;
StyledTextIterator(Print[] elements, Device device, GC gc) {
this.elements = new PrintIterator[elements.length];
for (int i = 0; i < elements.length; i++)
this.elements[i] = elements[i].iterator(device, gc);
minimumSize = computeSize(PrintSizeStrategy.MINIMUM);
preferredSize = computeSize(PrintSizeStrategy.PREFERRED);
}
private StyledTextIterator(StyledTextIterator that) {
elements = new PrintIterator[that.elements.length - that.cursor];
minimumSize = that.minimumSize;
preferredSize = that.preferredSize;
for (int i = 0; i < elements.length; i++)
elements[i] = that.elements[that.cursor + i].copy();
}
private Point computeSize(PrintSizeStrategy strategy) {
Point result = new Point(0, 0);
for (int i = 0; i < elements.length; i++) {
Point current = strategy.computeSize(elements[i]);
result.x = Math.max(result.x, current.x);
result.y = Math.max(result.y, current.y);
}
return result;
}
public Point minimumSize() {
return new Point(minimumSize.x, minimumSize.y);
}
public Point preferredSize() {
return new Point(preferredSize.x, preferredSize.y);
}
public boolean hasNext() {
advanceCursor();
return cursor < elements.length;
}
public PrintPiece next(int width, int height) {
if (width < 0 || height < 0)
return null;
int y = 0;
List rows = new ArrayList();
while (y < height) {
PrintPiece row = nextRow(width, height - y);
if (row == null)
break;
rows.add(new CompositeEntry(row, new Point(0, y)));
y += row.getSize().y;
}
if (rows.size() == 0)
return null;
return new CompositePiece(rows);
}
private PrintPiece nextRow(int width, int height) {
int x = 0;
int maxAscent = 0;
int maxDescent = 0;
final int backupCursor = cursor;
final List backup = new ArrayList();
List rowElements = new ArrayList();
while (hasNext()) { // hasNext advances cursor internally
PrintIterator element = elements[cursor];
Point preferredSize = element.preferredSize();
if (preferredSize.y > height)
break;
PrintIterator elementBackup = element.copy();
PrintPiece piece = PaperClips.next(element, width - x,
preferredSize.y);
if (piece == null)
break;
rowElements.add(piece);
backup.add(elementBackup);
maxAscent = Math.max(maxAscent, getAscent(piece));
maxDescent = Math.max(maxDescent, getDescent(piece));
if (maxAscent + maxDescent > height) {
restoreBackup(backupCursor, backup);
return null;
}
if (element.hasNext())
break;
x += piece.getSize().x;
}
return createRowResult(maxAscent, rowElements);
}
private PrintPiece createRowResult(int rowAscent, List rowElements) {
if (rowElements.size() == 0)
return null;
List entries = new ArrayList();
int x = 0;
for (int i = 0; i < rowElements.size(); i++) {
PrintPiece piece = (PrintPiece) rowElements.get(i);
int ascent = getAscent(piece);
entries.add(new CompositeEntry(piece, new Point(x, rowAscent
- ascent)));
x += piece.getSize().x;
}
return new CompositePiece(entries);
}
private void restoreBackup(final int backupCursor, final List backup) {
for (int i = 0; i < backup.size(); i++)
elements[backupCursor + i] = (PrintIterator) backup.get(i);
cursor = backupCursor;
}
private int getAscent(PrintPiece piece) {
if (piece instanceof TextPrintPiece)
return ((TextPrintPiece) piece).getAscent();
return piece.getSize().y;
}
private int getDescent(PrintPiece piece) {
if (piece instanceof TextPrintPiece)
return piece.getSize().y - ((TextPrintPiece) piece).getAscent();
return 0;
}
private void advanceCursor() {
while (cursor < elements.length && !elements[cursor].hasNext())
cursor++;
}
public PrintIterator copy() {
return new StyledTextIterator(this);
}
}