package com.xenoage.zong.renderer.awt.text;
import static com.xenoage.utils.collections.CollectionUtils.alist;
import static com.xenoage.utils.kernel.Range.range;
import static com.xenoage.utils.kernel.Tuple2.t;
import static com.xenoage.utils.kernel.Tuple3.t3;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import com.xenoage.utils.kernel.Tuple2;
import com.xenoage.utils.kernel.Tuple3;
import com.xenoage.utils.math.Units;
import com.xenoage.utils.math.geom.Point2f;
import com.xenoage.utils.math.geom.Rectangle2f;
/**
* This class combines several {@link TextLayout}s with
* positioning information in a single object.
*
* @author Andreas Wenger
*/
public class TextLayouts {
/**
* A {@link TextLayout} and its position.
*
* @author Andreas Wenger
*/
public static class Item {
public final TextLayout textLayout;
public final Point2f position;
public final int length;
public Item(TextLayout textLayout, Point2f position, int length) {
this.textLayout = textLayout;
this.position = position;
this.length = length;
}
}
public final ArrayList<Item> items;
public final Rectangle2f boundingRect;
public TextLayouts(ArrayList<Item> items) {
this.items = items;
//compute bounding rect
Rectangle2f boundingRect = null;
for (int i : range(items)) {
Item item = items.get(i);
Rectangle2f r = getTextLayoutBounds(item.textLayout).move(item.position);
if (boundingRect == null)
boundingRect = r;
else
boundingRect = boundingRect.extend(r);
}
this.boundingRect = boundingRect;
}
private Rectangle2f getTextLayoutBounds(TextLayout textLayout) {
Rectangle2D r = textLayout.getBounds();
return new Rectangle2f((float) r.getMinX(), (float) r.getMinY(), (float) r.getWidth(),
(float) r.getHeight());
}
public void draw(Graphics2D g) {
for (TextLayouts.Item item : items) {
//iText workaround: since vertical offset has reverted sign in Java2D and iText
//(at least here. this is confirmed by the author Paulo Soares, see
// http://sourceforge.net/mailarchive/message.php?msg_name=000601cae89a$d0ea15d0$587ba8c0%40psoaresw
//), we translate first, paint at y=0, and translate back
AffineTransform t = g.getTransform();
g.translate(item.position.x, item.position.y);
float scale = Units.pxToMm_1_1;
g.scale(scale, scale);
item.textLayout.draw(g, 0, 0);
g.setTransform(t);
}
}
/**
* Draws the given shape relative to the position of the given item.
*/
public void drawShape(Graphics2D g, Shape shape, Item item) {
AffineTransform t = g.getTransform();
g.translate(item.position.x, item.position.y);
float scale = Units.pxToMm_1_1;
g.scale(scale, scale);
g.draw(shape);
g.setTransform(t);
}
/**
* Fills the given shape relative to the position of the given item.
*/
public void fillShape(Graphics2D g, Shape shape, Item item) {
AffineTransform t = g.getTransform();
g.translate(item.position.x, item.position.y);
float scale = Units.pxToMm_1_1;
g.scale(scale, scale);
g.fill(shape);
g.setTransform(t);
}
/**
* Gets the {@link Item} and the local position within this item
* at the given global position. If not found, null is returned.
*/
public Tuple2<Item, Integer> getItemAt(int globalPosition) {
if (globalPosition >= 0) {
int pos = 0;
for (Item item : items) {
int length = item.length;
if (globalPosition <= pos + length) {
return t(item, globalPosition - pos);
}
pos += length;
}
}
return null;
}
/**
* Gets the {@link Item}s and the local positions within the first
* and last item. If not found, null is returned.
*/
public Tuple3<List<Item>, Integer, Integer> getItemsBetween(int leftGlobalPosition,
int rightGlobalPosition) {
List<Item> retItems = alist();
boolean leftFound = false;
int retLeft = 0, retRight = 0;
if (rightGlobalPosition >= leftGlobalPosition && leftGlobalPosition >= 0) {
int pos = 0;
for (Item item : items) {
int length = item.length;
if (!leftFound && leftGlobalPosition < pos + length) {
leftFound = true;
retLeft = leftGlobalPosition - pos;
retItems.add(item);
}
if (leftFound) {
if (pos + length >= rightGlobalPosition) {
retRight = rightGlobalPosition - pos;
return t3(retItems, retLeft, retRight);
}
else {
retItems.add(item);
}
}
pos += length;
}
}
return null;
}
/**
* Gets the global position from the given TextLayout and local position,
* or null if unknown.
*/
public Integer getGlobalPosition(TextLayout textLayout, int localPosition) {
int pos = 0;
for (Item item : items) {
if (textLayout == item.textLayout)
return pos + localPosition;
pos += item.length;
}
return null;
}
}