public class TextMetrics
{
public static final int ALIGN_LEFT = -1;
public static final int ALIGN_CENTER = -0;
public static final int ALIGN_RIGHT = +1;
public TextMetrics(String text)
{
this.text = text;
}
//
private final String text;
public String getText()
{
return this.text;
}
//
private int width;
public void setWidth(int width)
{
this.width = width;
}
public int getWidth()
{
return this.width;
}
//
private int alignment;
public void setAlignment(int alignment)
{
this.alignment = alignment;
}
public int getAlignment()
{
return this.alignment;
}
//
private boolean justify;
public void setJustified(boolean justify)
{
this.justify = justify;
}
public boolean isJustified()
{
return this.justify;
}
//
private Font font;
public void setFont(Font font)
{
this.font = font;
}
public Font getFont()
{
return this.font;
}
//
public List<WordMetric> calculate()
{
List<LineMetric> lines = calculateLineMetrics(this.text, this.font, this.width);
this.lineCount = lines.size();
return calculateWords(lines, this.font, this.width, this.alignment, this.justify);
}
private int lineCount;
public int getLineCount()
{
return this.lineCount;
}
public int getLineHeight()
{
return getMetrics(this.font).getHeight();
}
//
static ThreadLocal<Graphics> gtl;
static ThreadLocal<Map<Font, Map<Character, Rectangle2D>>> mtl;
static
{
gtl = new ThreadLocal<Graphics>()
{
@Override
protected Graphics initialValue()
{
return new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB).getGraphics();
}
};
mtl = new ThreadLocal<Map<Font, Map<Character, Rectangle2D>>>()
{
@Override
protected Map<Font, Map<Character, Rectangle2D>> initialValue()
{
return new HashMap<Font, Map<Character, Rectangle2D>>();
}
};
}
public static FontMetrics getMetrics(Font font)
{
return gtl.get().getFontMetrics(font);
}
public static Rectangle2D getArea(Font font, char c)
{
Map<Character, Rectangle2D> charToArea = mtl.get().get(font);
if (charToArea == null)
{
mtl.get().put(font, charToArea = new HashMap<Character, Rectangle2D>());
}
Rectangle2D area = charToArea.get(Character.valueOf(c));
if (area == null)
{
Graphics g = gtl.get();
FontMetrics fm = g.getFontMetrics(font);
area = fm.getStringBounds(new char[] { c }, 0, 1, g);
charToArea.put(Character.valueOf(c), area);
}
return area;
}
public static List<WordMetric> calculateWords(List<LineMetric> lines, Font font, int maxWidth, int align, boolean justify)
{
int h = getMetrics(font).getHeight();
int y = getMetrics(font).getAscent();
List<WordMetric> segments = new ArrayList<WordMetric>();
for (LineMetric trio : lines)
{
String lineText = trio.text;
double lineWidth = trio.width;
boolean lineFeed = trio.lineFeed;
if (justify && !lineFeed)
{
CharFeeder stuff2 = new CharFeeder(font);
for (char c : lineText.toCharArray())
stuff2.feed(c);
if (stuff2.currentWord.length() != 0)
stuff2.end(true);
double toFill = maxWidth - lineWidth;
int spaceCount = 0;
for (LineMetric word : stuff2.words)
if (word.text.equals(" "))
spaceCount++;
for (LineMetric word : stuff2.words)
if (word.text.equals(" "))
word.width += (toFill / spaceCount);
lineWidth = 0.0;
for (LineMetric word : stuff2.words)
lineWidth += word.width;
double x;
if (align == -1)
x = (maxWidth - lineWidth) * 0.0;
else if (align == ALIGN_CENTER)
x = (maxWidth - lineWidth) * 0.5;
else if (align == ALIGN_RIGHT)
x = (maxWidth - lineWidth) * 1.0;
else
throw new IllegalStateException();
for (LineMetric word : stuff2.words)
{
if (!word.text.trim().isEmpty())
segments.add(new WordMetric(word.text, x, y));
x += word.width;
}
}
else
{
double x;
if (align == ALIGN_LEFT)
x = (maxWidth - lineWidth) * 0.0;
else if (align == ALIGN_CENTER)
x = (maxWidth - lineWidth) * 0.5;
else if (align == ALIGN_RIGHT)
x = (maxWidth - lineWidth) * 1.0;
else
throw new IllegalStateException();
segments.add(new WordMetric(lineText, x, y));
}
y += h;
}
return segments;
}
public static List<LineMetric> calculateLineMetrics(String input, Font font, double maxWidth)
{
CharFeeder stuff = new CharFeeder(font);
for (char c : input.toCharArray())
{
stuff.feed(c);
}
if (stuff.currentWord.length() != 0)
{
stuff.end(true);
}
List<LineMetric> lines;
lines = new ArrayList<LineMetric>();
double currentWidth = 0.0;
StringBuilder currentLine = new StringBuilder();
for (LineMetric word : stuff.words)
{
String wordText = word.text;
double wordWidth = word.width;
boolean lineFeed = word.lineFeed;
if (currentLine.length() == 0 && wordText.equals(" "))
wordWidth = 0.0;
if (currentWidth + wordWidth > maxWidth)
{
String line = currentLine.toString();
if (!line.isEmpty() && line.charAt(0) == ' ')
{
line = line.substring(1);
currentWidth -= getArea(font, ' ').getWidth();
}
if (!line.isEmpty() && line.charAt(line.length() - 1) == ' ')
{
line = line.substring(0, line.length() - 1);
currentWidth -= getArea(font, ' ').getWidth();
}
lines.add(new LineMetric(line, currentWidth, false));
currentLine.setLength(0);
currentLine.trimToSize();
currentWidth = 0.0;
}
currentWidth += wordWidth;
currentLine.append(wordText);
if (lineFeed)
{
String line = currentLine.toString();
if (!line.isEmpty() && line.charAt(0) == ' ')
{
line = line.substring(1);
currentWidth -= getArea(font, ' ').getWidth();
}
if (!line.isEmpty() && line.charAt(line.length() - 1) == ' ')
{
line = line.substring(0, line.length() - 1);
currentWidth -= getArea(font, ' ').getWidth();
}
lines.add(new LineMetric(line, currentWidth, true));
currentLine.setLength(0);
currentLine.trimToSize();
currentWidth = 0.0;
}
}
if (currentLine.length() != 0)
{
String line = currentLine.toString();
if (!line.isEmpty() && line.charAt(0) == ' ')
{
line = line.substring(1);
currentWidth -= getArea(font, ' ').getWidth();
}
if (!line.isEmpty() && line.charAt(line.length() - 1) == ' ')
{
line = line.substring(0, line.length() - 1);
currentWidth -= getArea(font, ' ').getWidth();
}
lines.add(new LineMetric(line, currentWidth, true));
currentLine.setLength(0);
currentLine.trimToSize();
currentWidth = 0.0;
}
return lines;
}
static class CharFeeder
{
public final Font font;
public final List<LineMetric> words;
public CharFeeder(Font font)
{
this.font = font;
this.words = new ArrayList<LineMetric>();
}
double currentWidth = 0.0;
StringBuilder currentWord = new StringBuilder();
public void feed(char c)
{
if (c == '\n')
{
this.end(true);
return;
}
if (c < ' ')
{
throw new IllegalStateException("c=" + (int) c);
}
Rectangle2D area = TextMetrics.getArea(this.font, c);
if (c == ' ')
{
this.end(false);
}
this.currentWord.append(c);
this.currentWidth += area.getWidth();
if (c == ' ')
{
this.end(false);
}
}
public void end(boolean lineFeed)
{
String word = this.currentWord.toString();
this.words.add(new LineMetric(word, this.currentWidth, lineFeed));
this.currentWord.setLength(0);
this.currentWord.trimToSize();
this.currentWidth = 0.0;
}
}
}
How to use
String text = "...";
TextMetrics metrics = new TextMetrics(text);
metrics.setFont(new Font("Times New Roman", Font.ITALIC, 16));
metrics.setAlignment(ALIGN_RIGHT);
metrics.setJustified(true);
metrics.setWidth(500);
List<WordMetric> words = metrics.calculate();
int lineCount = metrics.getLineCount();
int lineHeight = metrics.getLineHeight();
int totalHeight = lineHeight * lineCount;
int m = 32;
int w = m + metrics.getWidth() + m;
int h = m + totalHeight + m;
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(Color.BLACK);
GraphicsUtil.enableAA(g); // mess with RenderingHints
g.translate(+m, +m);
g.setFont(metrics.getFont());
for (WordMetric word : words)
g.drawString(word.text, (int) Math.round(word.x), (int) Math.round(word.y));
g.translate(-m, -m);
ImageIO.write(img, "PNG", new File("./output.png"));
could you please post WordMetric and LineMetric?
Bwa! :o They used to be inner classes, so I assumed they were in the first post. :persecutioncomplex:
public class LineMetric
{
public String text;
public double width;
public boolean lineFeed;
public LineMetric(String text, double width, boolean lineFeed)
{
this.text = text;
this.width = width;
this.lineFeed = lineFeed;
}
}
public class WordMetric
{
public String text;
public double x, y;
public WordMetric(String text, double x, double y)
{
this.text = text;
this.x = x;
this.y = y;
}
}
Thanks, nice work