可能有針對此問題的解決方案(並且,順便說一下,也爲在other question這個問題)。它着眼於乍一看有點哈克,但我認爲是替代解決方案的優點和缺點:
計算與Font#getStringBounds
和邊界的Graphics2D
的FontRenderContext
了某些縮放因子完全錯誤的結果,因爲在此描述題。
計算使用「默認」(未轉換)FontRenderContext
(由StanislavL in his answer的建議)可能是一種選擇(略有調整),但是從other question描述的問題仍然遭受的邊界 - 即結果是錯誤的小字體(大小小於0.5)。
所以我延長與其他問題解決的途徑,造成兩名
鳥
漏洞與一個
石
黑客:而不是使用標準化字體大小爲1.0,我使用的是大的離譜字體,使用FontMetrics
對象計算邊界的大小,然後根據字體的原始大小縮小這些邊界。
這是總結在這個輔助類:
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
public class StringBoundsUtils
{
private static final Graphics2D DEFAULT_GRAPHICS;
static
{
BufferedImage bi = new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB);
DEFAULT_GRAPHICS = bi.createGraphics();
DEFAULT_GRAPHICS.setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
public static Rectangle2D computeStringBounds(String string, Font font)
{
return computeStringBounds(string, font, new Rectangle2D.Double());
}
public static Rectangle2D computeStringBounds(
String string, Font font, Rectangle2D result)
{
final float helperFontSize = 1000.0f;
final float fontSize = font.getSize2D();
final float scaling = fontSize/helperFontSize;
Font helperFont = font.deriveFont(helperFontSize);
FontMetrics fontMetrics = DEFAULT_GRAPHICS.getFontMetrics(helperFont);
double stringWidth = fontMetrics.stringWidth(string) * scaling;
double stringHeight = fontMetrics.getHeight() * scaling;
if (result == null)
{
result = new Rectangle2D.Double();
}
result.setRect(
0, -fontMetrics.getAscent() * scaling,
stringWidth, stringHeight);
return result;
}
}
(這可以擴展/調整使用給定Graphics2D
對象,但應該然後驗證縮放不會影響FontMetrics
以及.. )
我很確定有些情況下,這不起作用:從右到左的文本,中文字符或所有FontMetrics
的內部工作不足以測量文本大小的情況適當。但它適用於所有與我相關的案例(也可能適用於其他許多案例),而且它確實會受到上述錯誤的影響,而且它的確很快。
這裏是一個非常簡單的性能對比(不是一個真正的基準,但應該給一個粗略的指標):
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.util.Locale;
public class StringBoundsUtilsPerformance
{
public static void main(String[] args)
{
String strings[] = {
"a", "AbcXyz", "AbCdEfGhIjKlMnOpQrStUvWxYz",
"AbCdEfGhIjKlMnOpQrStUvWxYz" +
"AbCdEfGhIjKlMnOpQrStUvWxYz" +
"AbCdEfGhIjKlMnOpQrStUvWxYz" +
"AbCdEfGhIjKlMnOpQrStUvWxYz" +
"AbCdEfGhIjKlMnOpQrStUvWxYz" };
float fontSizes[] = { 1.0f, 10.0f, 100.0f };
int runs = 1000000;
long before = 0;
long after = 0;
double resultA = 0;
double resultB = 0;
for (float fontSize : fontSizes)
{
Font font = new Font("Dialog", Font.PLAIN, 10).deriveFont(fontSize);
for (String string : strings)
{
before = System.nanoTime();
for (int i=0; i<runs; i++)
{
Rectangle2D r = computeStringBoundsDefault(string, font);
resultA += r.getWidth();
}
after = System.nanoTime();
resultA /= runs;
System.out.printf(Locale.ENGLISH,
"A: time %14.4f result %14.4f, fontSize %3.1f, length %d\n",
(after-before)/1e6, resultA, fontSize, string.length());
before = System.nanoTime();
for (int i=0; i<runs; i++)
{
Rectangle2D r =
StringBoundsUtils.computeStringBounds(string, font);
resultB += r.getWidth();
}
after = System.nanoTime();
resultB /= runs;
System.out.printf(Locale.ENGLISH,
"B: time %14.4f result %14.4f, fontSize %3.1f, length %d\n",
(after-before)/1e6, resultB, fontSize, string.length());
}
}
}
private static final FontRenderContext DEFAULT_FONT_RENDER_CONTEXT =
new FontRenderContext(null, true, true);
public static Rectangle2D computeStringBoundsDefault(
String string, Font font)
{
return font.getStringBounds(string, DEFAULT_FONT_RENDER_CONTEXT);
}
}
它計算具有不同長度和字體大小的字符串的界限,以及定時結果大致如下的行:
A: time 1100.4441 result 14.7813, fontSize 1.0, length 26
B: time 218.6409 result 14.7810, fontSize 1.0, length 26
...
A: time 1167.1569 result 147.8125, fontSize 10.0, length 26
B: time 200.6532 result 147.8100, fontSize 10.0, length 26
...
A: time 1179.7873 result 1478.1253, fontSize 100.0, length 26
B: time 208.9414 result 1478.1003, fontSize 100.0, length 26
所以StringBoundsUtils
是由5倍(甚至更長的字符串)比Font#getStringBounds
方法快。
上述輸出中的result
列已經表明用Font#getStringBounds
計算的邊界寬度與用這些StringBoundsUtils
計算的邊界寬度之間的差異可以忽略不計。
但是,我想確保這不僅適用於widhts,而且適用於整個界限。所以,我創建了一個小測試:
在這個例子中,我們可以看到,邊界是「幾乎等於」對於這兩種方法,無論縮放和字體大小的 - 當然,認爲即使字體大小小於0.5,StringBoundsUtils
也會計算出適當的邊界。
該測試的源代碼,爲了完整性:(它使用了一個小Viewer library,所述Viewer JAR is in Maven Central)
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.Locale;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import de.javagl.viewer.Painter;
import de.javagl.viewer.Viewer;
public class StringBoundsUtilsTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static final Font DEFAULT_FONT =
new Font("Dialog", Font.PLAIN, 10);
private static Font font = DEFAULT_FONT.deriveFont(10f);
private static void createAndShowGUI()
{
JFrame f = new JFrame("Viewer");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
Viewer viewer = new Viewer();
String string = "AbcXyz";
viewer.addPainter(new Painter()
{
@Override
public void paint(Graphics2D g, AffineTransform worldToScreen,
double w, double h)
{
AffineTransform at = g.getTransform();
g.setColor(Color.BLACK);
g.setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Rectangle2D boundsA =
StringBoundsUtilsPerformance.computeStringBoundsDefault(
string, font);
Rectangle2D boundsB =
StringBoundsUtils.computeStringBounds(string, font);
g.setFont(new Font("Monospaced", Font.BOLD, 12));
g.setColor(Color.GREEN);
g.drawString(createString(boundsA), 10, 20);
g.setColor(Color.RED);
g.drawString(createString(boundsB), 10, 40);
g.setFont(font);
g.transform(worldToScreen);
g.drawString(string, 0, 0);
g.setTransform(at);
g.setColor(Color.GREEN);
g.draw(worldToScreen.createTransformedShape(boundsA));
g.setColor(Color.RED);
g.draw(worldToScreen.createTransformedShape(boundsB));
}
});
f.getContentPane().add(viewer, BorderLayout.CENTER);
f.getContentPane().add(
new JLabel("Mouse wheel: Zoom, "
+ "Right mouse drags: Move, "
+ "Left mouse drags: Rotate"),
BorderLayout.NORTH);
JSpinner fontSizeSpinner =
new JSpinner(new SpinnerNumberModel(10.0, 0.1, 100.0, 0.1));
fontSizeSpinner.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
Object object = fontSizeSpinner.getValue();
Number number = (Number)object;
float fontSize = number.floatValue();
font = DEFAULT_FONT.deriveFont(fontSize);
viewer.repaint();
}
});
JPanel p = new JPanel();
p.add(new JLabel("Font size"), BorderLayout.WEST);
p.add(fontSizeSpinner, BorderLayout.CENTER);
f.getContentPane().add(p, BorderLayout.SOUTH);
viewer.setPreferredSize(new Dimension(1000,500));
viewer.setDisplayedWorldArea(-15,-15,30,30);
f.pack();
viewer.setPreferredSize(null);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static String createString(Rectangle2D r)
{
return String.format(Locale.ENGLISH,
"x=%12.4f y=%12.4f w=%12.4f h=%12.4f",
r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
}
我傾向於'String'表示形式轉換爲'Shape'然後應用所有變換(例如縮放)到「形狀」。 –
@AndrewThompson這可能工作,但例如創建一個'GlyphVector'並獲取其「邏輯範圍」(如鏈接問題中所做的)是相當昂貴的 - 沒有人願意爲數百或數千個標籤做。我覺得很奇怪,像這樣一個比較簡單的任務就像獲得邊界那麼困難(並且似乎涉及很多微妙的錯誤)。 – Marco13
*「沒有人願意爲數百或數千個標籤做。」*爲自己說話。我,在進入「過早優化」之前,我會嘗試一下。 *「我認爲這樣一個相對(!)簡單的任務很奇怪」*與什麼相比?創造世界和平?這比用文字描述任務複雜得多。 –