Получение размера строки в java (без наличия объекта Graphics)

Я пытаюсь написать приложение, которое должно рисовать много строк с использованием класса Graphics2D в Java. Мне нужно получить размеры каждого объекта String (чтобы вычислить точное положение каждой строки). Существует так много строк, что это должно быть сделано до вызова метода paint() и только один раз в начале моей программы (так что у меня пока нет объекта Graphics2D). Я знаю, что существует метод Font.getStringBounds(), но ему нужен объект FontRenderContext в качестве параметра.

Когда я попытался создать свой собственный объект:

FontRenderContext frc = new FontRenderContext(MyFont.getTransform(), true, true)

а затем получить границы строк, я всегда получаю разные размеры, чем когда я получаю FontRenderContext, используя метод Graphics2D.getFontRenderContext() внутри метода paint(). Различия невелики (около 1E-3), но мне интересно, почему есть разница?

Однако есть ли лучший и безопасный способ получения размеров строки?

Thnx за любую помощь заранее!

Ответ 1

Попробуйте FontMetrics класс; метод stringWidth возвращает размер строки. Пример:

JComponent c = getSomeKindOfJComponent();
FontMetrics fm = c.getFontMetrics(c.getFont()); // or another font
int strw = fm.stringWidth("My text");

Ответ 2

Вы также можете проверить SwingUtilities.computeStringWidth.

Ответ 3

штат Невада-ах. Гон-на. Случаются.

Причина заключается в том, что рендеринг и вычисления, которые вы ищете из FRC, относятся к контексту Graphics, то есть к определенному объекту Graphics2D. Те, кого вас интересует, это тот, который вы передали во время выполнения - он не похож ни на что другой (вы должны предположить).

Вы можете вычислить столько, сколько хотите, используя FRC из другого Graphics2D, но все ваши вычисления без изменений, когда вы пытаетесь использовать их во время выполнения с GraphicsCDC GraphicsComponent, - это Graphics2D, который вы собираетесь использование, несмотря ни на что.

Итак, да, это было бы неплохо, но это было бы полностью теоретическим. Вся эта приятная информация эффективно блокируется внутри этого FRC, потому что без точной Graphics2D на самом деле будет нарисован AttributedString, что FRC хуже, чем бесполезно - это иллюзия, которую вы могли бы попытаться охватить.

Это имеет смысл, поскольку все действительно зависит от Graphics2D, которое вы получаете во время выполнения. Поэтому самое лучшее, что нужно сделать, это просто принять его и написать свой код, чтобы вызывать из paintComponent любые объекты и любые специализированные вычисления, которые вы должны делать, и строить свой дизайн вокруг того факта, что это так, как есть.

У меня хороший вопрос и хорошая вещь, которую вы могли бы пожелать, просто вы не можете. Вы видите, как другие люди просят об этом в другом месте в Интернете, на других форумах. Обратите внимание на отсутствие полезных ответов и/или оглушительную тишину.

Ответ 4

Помимо использования FontMetrics, JLabel можно использовать для определения размера как неформатированного, так и (базового HTML) визуализированного текста. Вот пример.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import java.awt.image.BufferedImage;

import javax.swing.JOptionPane;
import javax.swing.JLabel;
import javax.swing.ImageIcon;

/** Sample code obtained from a thread on the Oracle forums that I cannot
locate at this instant.  My question was related to an unexpected rendering of
JLabel.  It was resolved by the 'added this' line courtesy of Darryl Burke. */
public class LabelRenderTest {

  String title = "<html><body style='width: 160px; padding: 8px'>"
          + "<h1>Do U C Me?</h1>"
          + "Here is a long string that will wrap.  "
          + "The effect we want is a multi-line label.";

  LabelRenderTest() {
    BufferedImage image = new BufferedImage(
            640,
            480,
            BufferedImage.TYPE_INT_RGB);
    Graphics2D imageGraphics = image.createGraphics();
    GradientPaint gp = new GradientPaint(
            20f, 20f, Color.blue,
            620f, 460f, Color.white);
    imageGraphics.setPaint(gp);
    imageGraphics.fillRect(0, 0, 800, 600);

    JLabel textLabel = new JLabel(title);
    textLabel.setSize(textLabel.getPreferredSize()); // <==== added this

    Dimension d = textLabel.getPreferredSize();
    BufferedImage bi = new BufferedImage(
            d.width,
            d.height,
            BufferedImage.TYPE_INT_ARGB);
    Graphics g = bi.createGraphics();
    g.setColor(new Color(255, 255, 255, 128));
    g.fillRoundRect(
            0,
            0,
            bi.getWidth(null),
            bi.getHeight(null),
            15,
            10);
    g.setColor(Color.black);
    textLabel.paint(g);
    Graphics g2 = image.getGraphics();
    g2.drawImage(bi, 20, 20, null);

    ImageIcon ii = new ImageIcon(image);
    JLabel imageLabel = new JLabel(ii);

    JOptionPane.showMessageDialog(null, imageLabel);
  }

  public static void main(String[] args) {
    LabelRenderTest ist = new LabelRenderTest();
  }
}

Изменить 1: Что касается вашего комментария "много строк". Нарисуйте строки в BufferedImage который восстанавливается только при необходимости. Используйте буферизованное изображение каждый раз, когда вызывается paintComponent().

Ответ 5

Вот фрагмент кода, который делает что-то подобное - написал его для сокращения строки до заданного количества пикселей.

public static String abbreviate(final Graphics2D g2, final String text, final int fitToWidth) {
     // define how many characters in the caption can be drawn
     final FontMetrics fm = g2.getFontMetrics();
     Rectangle2D textBounds = fm.getStringBounds(text, g2);
     int count = text.length();
     while ((textBounds.getWidth() > fitToWidth) && (count > 4)) {
         textBounds = fm.getStringBounds(text.substring(0, count--), g2);
     }
     return count == text.length() ? text : StringUtils.abbreviate(text, count);
}

Ответ 6

для исторической цели, вот как я думаю, что он сделал это изначально (jruby java pseucodoe)

font = UIManager.getFont("Label.font")
frc = java.awt.font.FontRenderContext.new(font.transform, true, true)
textLayout = java.awt.font.TextLayout.new(text, font, frc)
textLayout.bounds.width