package com.positive.charts.util;
import java.text.BreakIterator;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Transform;
public class TextUtilities {
/**
* Creates a {@link TextBlock} from a <code>String</code>. Line breaks are
* added where the <code>String</code> contains '\n' characters.
*
* @param text
* the text.
* @param font
* the font.
* @param paint
* the paint.
*
* @return A text block.
*/
public static TextBlock createTextBlock(final String text, final Font font,
final Color paint) {
if (text == null) {
throw new IllegalArgumentException("Null 'text' argument.");
}
final TextBlock result = new TextBlock();
String input = text;
boolean moreInputToProcess = (text.length() > 0);
final int start = 0;
while (moreInputToProcess) {
final int index = input.indexOf("\n");
if (index > start) {
final String line = input.substring(start, index);
if (index < input.length() - 1) {
result.addLine(line, font, paint);
input = input.substring(index + 1);
} else {
moreInputToProcess = false;
}
} else if (index == start) {
if (index < input.length() - 1) {
input = input.substring(index + 1);
} else {
moreInputToProcess = false;
}
} else {
result.addLine(input, font, paint);
moreInputToProcess = false;
}
}
return result;
}
/**
* Creates a new text block from the given string, breaking the text into
* lines so that the <code>maxWidth</code> value is respected.
*
* @param text
* the text.
* @param font
* the font.
* @param paint
* the paint.
* @param maxWidth
* the maximum width for each line.
* @param maxLines
* the maximum number of lines.
* @param measurer
* the text measurer.
*
* @return A text block.
*/
public static TextBlock createTextBlock(final String text, final Font font,
final Color paint, final float maxWidth, final int maxLines,
final TextMeasurer measurer) {
final TextBlock result = new TextBlock();
final BreakIterator iterator = BreakIterator.getLineInstance();
iterator.setText(text);
int current = 0;
int lines = 0;
final int length = text.length();
while ((current < length) && (lines < maxLines)) {
final int next = nextLineBreak(text, current, maxWidth, iterator,
measurer);
if (next == BreakIterator.DONE) {
result.addLine(text.substring(current), font, paint);
return result;
}
result.addLine(text.substring(current, next), font, paint);
lines++;
current = next;
while ((current < text.length()) && (text.charAt(current) == '\n')) {
current++;
}
}
if (current < length) {
final TextLine lastLine = result.getLastLine();
final TextFragment lastFragment = lastLine.getLastTextFragment();
final String oldStr = lastFragment.getText();
String newStr = "...";
if (oldStr.length() > 3) {
newStr = oldStr.substring(0, oldStr.length() - 3) + "...";
}
lastLine.removeFragment(lastFragment);
final TextFragment newFragment = new TextFragment(newStr,
lastFragment.getFont(), lastFragment.getPaint());
lastLine.addFragment(newFragment);
}
return result;
}
/**
* A utility method that calculates the rotation anchor offsets for a
* string. These offsets are relative to the text starting coordinate
* (BASELINE_LEFT).
*
* @param g2
* the graphics device.
* @param text
* the text.
* @param anchor
* the anchor point.
*
* @return The offsets.
*/
private static float[] deriveRotationAnchorOffsets(final GC g2,
final String text, final TextAnchor anchor) {
final float[] result = new float[2];
final FontMetrics fm = g2.getFontMetrics();
final Rectangle bounds = TextUtilities.getTextBounds(text, g2);
final float ascent = fm.getAscent();
final float halfAscent = ascent / 2.0f;
final float descent = fm.getDescent();
final float leading = fm.getLeading();
float xAdj = 0.0f;
float yAdj = 0.0f;
if ((anchor == TextAnchor.TOP_LEFT)
|| (anchor == TextAnchor.CENTER_LEFT)
|| (anchor == TextAnchor.BOTTOM_LEFT)
|| (anchor == TextAnchor.BASELINE_LEFT)
|| (anchor == TextAnchor.HALF_ASCENT_LEFT)) {
xAdj = 0.0f;
} else if ((anchor == TextAnchor.TOP_CENTER)
|| (anchor == TextAnchor.CENTER)
|| (anchor == TextAnchor.BOTTOM_CENTER)
|| (anchor == TextAnchor.BASELINE_CENTER)
|| (anchor == TextAnchor.HALF_ASCENT_CENTER)) {
xAdj = bounds.width / 2.0f;
} else if ((anchor == TextAnchor.TOP_RIGHT)
|| (anchor == TextAnchor.CENTER_RIGHT)
|| (anchor == TextAnchor.BOTTOM_RIGHT)
|| (anchor == TextAnchor.BASELINE_RIGHT)
|| (anchor == TextAnchor.HALF_ASCENT_RIGHT)) {
xAdj = bounds.width;
}
if ((anchor == TextAnchor.TOP_LEFT)
|| (anchor == TextAnchor.TOP_CENTER)
|| (anchor == TextAnchor.TOP_RIGHT)) {
yAdj = descent + leading - bounds.height;
} else if ((anchor == TextAnchor.CENTER_LEFT)
|| (anchor == TextAnchor.CENTER)
|| (anchor == TextAnchor.CENTER_RIGHT)) {
yAdj = descent + leading - (float) (bounds.height / 2.0);
} else if ((anchor == TextAnchor.HALF_ASCENT_LEFT)
|| (anchor == TextAnchor.HALF_ASCENT_CENTER)
|| (anchor == TextAnchor.HALF_ASCENT_RIGHT)) {
yAdj = -halfAscent;
} else if ((anchor == TextAnchor.BASELINE_LEFT)
|| (anchor == TextAnchor.BASELINE_CENTER)
|| (anchor == TextAnchor.BASELINE_RIGHT)) {
yAdj = 0.0f;
} else if ((anchor == TextAnchor.BOTTOM_LEFT)
|| (anchor == TextAnchor.BOTTOM_CENTER)
|| (anchor == TextAnchor.BOTTOM_RIGHT)) {
yAdj = fm.getDescent() + fm.getLeading();
}
result[0] = xAdj;
result[1] = yAdj;
return result;
}
/**
* A utility method that calculates the anchor offsets for a string.
* Normally, the (x, y) coordinate for drawing text is a point on the
* baseline at the left of the text string. If you add these offsets to (x,
* y) and draw the string, then the anchor point should coincide with the
* (x, y) point.
*
* @param g2
* the graphics device (not <code>null</code>).
* @param text
* the text.
* @param anchor
* the anchor point.
* @param textBounds
* the receiver for resulting text bounds
*
* @return The offsets.
*/
private static float[] deriveTextBoundsAnchorOffsets(final GC g2,
final String text, final TextAnchor anchor,
final Rectangle textBounds) {
final float[] result = new float[2];
final FontMetrics fm = g2.getFontMetrics();
final Rectangle bounds = TextUtilities.getTextBounds(text, g2);
final float ascent = fm.getAscent();
final float halfAscent = ascent / 2.0f;
final float leading = fm.getLeading();
float xAdj = 0.0f;
float yAdj = 0.0f;
// Horizontal adjustment
if (anchor.isCenterAligned()) {
xAdj = -bounds.width / 2.0f;
} else if (anchor.isRightAligned()) {
xAdj = -bounds.width;
}
// Vertical adjustment
if (anchor.isTopAligned()) {
yAdj = 0.0f;
} else if ((anchor == TextAnchor.HALF_ASCENT_LEFT)
|| (anchor == TextAnchor.HALF_ASCENT_CENTER)
|| (anchor == TextAnchor.HALF_ASCENT_RIGHT)) {
yAdj = -leading - halfAscent;
} else if (anchor.isVerticallyCenterAligned()) {
yAdj = (float) (-bounds.height / 2.0);
} else if ((anchor == TextAnchor.BASELINE_LEFT)
|| (anchor == TextAnchor.BASELINE_CENTER)
|| (anchor == TextAnchor.BASELINE_RIGHT)) {
yAdj = -leading - ascent;
} else if ((anchor == TextAnchor.BOTTOM_LEFT)
|| (anchor == TextAnchor.BOTTOM_CENTER)
|| (anchor == TextAnchor.BOTTOM_RIGHT)) {
yAdj = -bounds.height;
}
if (textBounds != null) {
RectangleUtil.setRect(textBounds, bounds);
}
result[0] = xAdj;
result[1] = yAdj;
return result;
}
/**
* Draws a string such that the specified anchor point is aligned to the
* given (x, y) location.
*
* @param text
* the text.
* @param g2
* the graphics device.
* @param x
* the x coordinate (Java 2D).
* @param y
* the y coordinate (Java 2D).
* @param anchor
* the anchor location.
*
* @return The text bounds (adjusted for the text position).
*/
public static Rectangle drawAlignedString(final String text, final GC g2,
final float x, final float y, final TextAnchor anchor) {
final Rectangle textBounds = new Rectangle(0, 0, 0, 0);
final float[] adjust = deriveTextBoundsAnchorOffsets(g2, text, anchor,
textBounds);
// adjust text bounds to match string position
RectangleUtil.setRect(textBounds, (int) (x + adjust[0]),
(int) (y + adjust[1]), textBounds.width, textBounds.height);
g2.drawString(text, textBounds.x, textBounds.y, true);
return textBounds;
}
/**
* A utility method for drawing rotated text.
* <P>
* A common rotation is -Math.PI/2 which draws text 'vertically' (with the
* top of the characters on the left).
*
* @param text
* the text.
* @param g2
* the graphics device.
* @param angle
* the angle of the (clockwise) rotation (in radians).
* @param x
* the x-coordinate.
* @param y
* the y-coordinate.
*/
public static void drawRotatedString(final String text, final GC g2,
final double angle, final float x, final float y) {
drawRotatedString(text, g2, x, y, angle, x, y);
}
/**
* A utility method for drawing rotated text.
* <P>
* A common rotation is -Math.PI/2 which draws text 'vertically' (with the
* top of the characters on the left).
*
* @param text
* the text.
* @param g2
* the graphics device.
* @param textX
* the x-coordinate for the text (before rotation).
* @param textY
* the y-coordinate for the text (before rotation).
* @param angle
* the angle of the (clockwise) rotation (in radians).
* @param rotateX
* the point about which the text is rotated.
* @param rotateY
* the point about which the text is rotated.
*/
public static void drawRotatedString(final String text, final GC g2,
final float textX, final float textY, final double angle,
final float rotateX, final float rotateY) {
if ((text == null) || (text.length() == 0)) {
return;
}
if (angle != 0.0) {
final Transform transform = new Transform(g2.getDevice());
transform.translate(rotateX, rotateY);
transform.rotate((float) (-angle * 180 / Math.PI));
transform.translate(-rotateX, -rotateY);
g2.setTransform(transform);
transform.dispose();
}
g2.drawString(text, (int) textX, (int) textY, true);
if (angle != 0.0) {
g2.setTransform(null);
}
}
/**
* Draws a string that is aligned by one anchor point and rotated about
* another anchor point.
*
* @param text
* the text.
* @param g2
* the graphics device.
* @param x
* the x-coordinate for positioning the text.
* @param y
* the y-coordinate for positioning the text.
* @param textAnchor
* the text anchor.
* @param angle
* the rotation angle.
* @param rotationX
* the x-coordinate for the rotation anchor point.
* @param rotationY
* the y-coordinate for the rotation anchor point.
*/
public static void drawRotatedString(final String text, final GC g2,
final float x, final float y, final TextAnchor textAnchor,
final double angle, final float rotationX, final float rotationY) {
if ((text == null) || (text.length() == 0)) {
return;
}
final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
textAnchor, null);
drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], angle,
rotationX, rotationY);
}
/**
* Draws a string that is aligned by one anchor point and rotated about
* another anchor point.
*
* @param text
* the text.
* @param g2
* the graphics device.
* @param x
* the x-coordinate for positioning the text.
* @param y
* the y-coordinate for positioning the text.
* @param textAnchor
* the text anchor.
* @param angle
* the rotation angle (in radians).
* @param rotationAnchor
* the rotation anchor.
*/
public static void drawRotatedString(final String text, final GC g2,
final float x, final float y, final TextAnchor textAnchor,
final double angle, final TextAnchor rotationAnchor) {
if ((text == null) || (text.length() == 0)) {
return;
}
final float[] textAdj = deriveTextBoundsAnchorOffsets(g2, text,
textAnchor, null);
final float[] rotateAdj = deriveRotationAnchorOffsets(g2, text,
rotationAnchor);
// final float[] rotateAdj = new float[] { 0.0f, 0.0f };
drawRotatedString(text, g2, x + textAdj[0], y + textAdj[1], angle, x
+ textAdj[0] + rotateAdj[0], y + textAdj[1] + rotateAdj[1]);
}
/**
* Returns the bounds for the specified text.
*
* @param text
* the text (<code>null</code> permitted).
* @param g2
* the graphics context (not <code>null</code>).
* @return The text bounds (<code>null</code> if the <code>text</code>
* argument is <code>null</code>).
*
*/
public static Rectangle getTextBounds(final String text, final GC g2) {
final Point extent = g2.textExtent(text);
return new Rectangle(0, 0, extent.x, extent.y);
}
/**
* Returns the character index of the next line break.
*
* @param text
* the text.
* @param start
* the start index.
* @param width
* the target display width.
* @param iterator
* the word break iterator.
* @param measurer
* the text measurer.
*
* @return The index of the next line break.
*/
private static int nextLineBreak(final String text, final int start,
final float width, final BreakIterator iterator,
final TextMeasurer measurer) {
// this method is (loosely) based on code in JFreeReport's
// TextParagraph class
int current = start;
int end;
float x = 0.0f;
boolean firstWord = true;
int newline = text.indexOf('\n', start);
if (newline < 0) {
newline = Integer.MAX_VALUE;
}
while (((end = iterator.next()) != BreakIterator.DONE)) {
if (end > newline) {
return newline;
}
x += measurer.getStringWidth(text, current, end);
if (x > width) {
if (firstWord) {
while (measurer.getStringWidth(text, start, end) > width) {
end--;
if (end <= start) {
return end;
}
}
return end;
} else {
end = iterator.previous();
return end;
}
}
// we found at least one word that fits ...
firstWord = false;
current = end;
}
return BreakIterator.DONE;
}
/**
* Private constructor prevents object creation.
*/
private TextUtilities() {
}
}