Java - comment centrer visuellement une chaîne de caractères (pas seulement une police) dans un rectangle

j'essaie de centrer visuellement une chaîne arbitraire fournie par l'utilisateur sur un JPanel. J'ai lu des dizaines d'autres questions et réponses similaires ici, mais n'en ai pas trouvé qui traitent directement le problème que j'ai.

dans l'exemple de code ci-dessous, getWidth() et getHeight() font référence à la largeur et à la hauteur du JPanel sur lequel je place la chaîne de texte. J'ai trouvé que TextLayout.getBounds() fait un très bon travail de me dire la taille d'un rectangle délimitant le texte. Donc, j'ai pensé qu'il serait relativement simple pour centrer le texte rectangle dans le Composite rectangle en calculant les positions x et y sur le Composite de l'angle inférieur gauche du texte-rectangle:

FontRenderContext context = g2d.getFontRenderContext();
messageTextFont = new Font("Arial", Font.BOLD, fontSize);
TextLayout txt = new TextLayout(messageText, messageTextFont, context);
Rectangle2D bounds = txt.getBounds();
xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2));

g2d.setFont(messageTextFont);
g2d.setColor(rxColor);
g2d.drawString(messageText, xString, yString);

cela fonctionnait parfaitement pour les cordes qui étaient toutes majuscules. Cependant, quand j'ai commencé à tester avec des chaînes qui contenaient des lettres minuscules avec des descenders( comme g, p, y), le texte n'était pas plus centré. Les descenders sur les lettres minuscules (les parties qui s'étendent au-dessous de la ligne de base de la police) étaient dessinés trop bas sur le JPanel pour avoir le texte semble être centré.

C'est là que j'ai découvert (grâce à SO) que le paramètre y passé à drawString () spécifie la baseline du texte dessiné, pas la limite inférieure. Ainsi, encore une fois avec L'aide de SO, j'ai réalisé que j'avais besoin d'ajuster le le placement du texte par la longueur des prolongements dans ma chaîne:

....
    TextLayout txt = new TextLayout(messageText, messageTextFont, context);
    Rectangle2D bounds = txt.getBounds();
    int descent = (int)txt.getDescent();
    xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
    yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2) - descent);
....

j'ai testé cela avec des cordes lourdes en lettres minuscules comme g, p, et y et ça a bien fonctionné! WooHoo! Mais....attendre. Ugh. Maintenant, quand j'essaie avec seulement des lettres majuscules, le texte est beaucoup trop haut sur le JPanel pour avoir l'air centré.

c'est là que j'ai découvert que TextLayout.getDescent () (et toutes les autres méthodes getDescent() que j'ai trouvées pour autres classes) renvoie la descente maximale de la FONT pas de la chaîne spécifique. Ainsi, ma chaîne de caractères majuscules était soulevée pour tenir compte des descendeurs qui ne se sont même pas produits dans cette chaîne.

Que dois-je faire? Si je n'ajuste pas le paramètre y de drawString() pour tenir compte des descenders, les strings minuscules avec descenders sont visuellement trop bas sur le JPanel. Si j'ajuste le paramètre y pour drawString () pour tenir compte des descendeurs puis les chaînes qui ne contiennent pas de caractères avec descenders sont visuellement trop hautes sur le JPanel. Il ne semble pas y avoir de moyen pour moi de déterminer où est la ligne de base dans le rectangle délimitant le texte pour une chaîne donnée. Ainsi, Je ne peux pas comprendre exactement ce que y passer à drawString ().

Merci pour toute aide ou suggestion.

12
demandé sur MadProgrammer 2014-05-19 08:57:04

2 réponses

pendant que je me faufile avec TextLayout , vous pouvez juste utiliser le Graphics de contexte FontMetrics , par exemple...

Text

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

    public static void main(String[] args) {
        new LayoutText();
    }

    public LayoutText() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private String text;

        public TestPane() {
            text = "Along time ago, in a galaxy, far, far away";
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setColor(Color.RED);
            g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
            g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

            Font font = new Font("Arial", Font.BOLD, 48);
            g2d.setFont(font);
            FontMetrics fm = g2d.getFontMetrics();
            int x = ((getWidth() - fm.stringWidth(text)) / 2);
            int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();

            g2d.setColor(Color.BLACK);
            g2d.drawString(text, x, y);

            g2d.dispose();
        }
    }

}

OK, après quelques histoires...

essentiellement, le rendu de texte se produit à la base, ce qui rend la position y des limites apparaissent habituellement au-dessus de ce point, ce qui le fait ressembler au texte a été peint au-dessus de la y position

pour surmonter cela, nous devons ajouter la montée de la police moins la descente de la police à la position y ...

par exemple...

FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);

Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();

... C'est pourquoi j'aime rendre le texte à la main ...

Praticable exemple...

Layout

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

    public static void main(String[] args) {
        new LayoutText();
    }

    public LayoutText() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private String text;

        public TestPane() {
            text = "Along time ago, in a galaxy, far, far away";
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setColor(Color.RED);
            g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
            g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

            FontRenderContext context = g2d.getFontRenderContext();
            Font font = new Font("Arial", Font.BOLD, 48);
            TextLayout txt = new TextLayout(text, font, context);

            Rectangle2D bounds = txt.getBounds();
            int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
            int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
            y += txt.getAscent() - txt.getDescent();

            g2d.setFont(font);
            g2d.setColor(Color.BLACK);
            g2d.drawString(text, x, y);

            g2d.setColor(Color.BLUE);
            g2d.translate(x, y);
            g2d.draw(bounds);

            g2d.dispose();
        }
    }

}

regardez travailler avec des API texte pour plus d'informations...

mise à Jour

comme cela a déjà été suggéré, vous pouvez utiliser un GlyphVector ...

chaque mot ( Cat et Dog ) est calculé séparément pour démontrer les différences

CatDog

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class LayoutText {

    public static void main(String[] args) {
        new LayoutText();
    }

    public LayoutText() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private String text;

        public TestPane() {
            text = "A long time ago, in a galaxy, far, far away";
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.setColor(Color.RED);
            g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
            g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);

            Font font = new Font("Arial", Font.BOLD, 48);
            g2d.setFont(font);

            FontRenderContext frc = g2d.getFontRenderContext();
            GlyphVector gv = font.createGlyphVector(frc, "Cat");
            Rectangle2D box = gv.getVisualBounds();

            int x = 0;
            int y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
            g2d.drawString("Cat", x, y);

            x += box.getWidth();

            gv = font.createGlyphVector(frc, "Dog");
            box = gv.getVisualBounds();

            y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
            g2d.drawString("Dog", x, y);

            g2d.dispose();
        }
    }

}
20
répondu MadProgrammer 2014-05-19 06:30:33

je pense cette réponse est la bonne façon de le faire, cependant, j'ai eu des problèmes dans le passé avec des polices personnalisées et l'obtention de leurs limites. Dans un projet, j'ai dû recourir à l'obtention du contour de la police et à l'utilisation de ces limites. Cette méthode est probablement plus intensive en mémoire mais elle semble être un moyen sûr pour obtenir des limites de police.

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Font font = new Font("Arial", Font.BOLD, 48);
    String text = "Along time ago, in a galaxy, far, far away";

    Shape outline = font.createGlyphVector(g.getFontMetrics().getFontRenderContext(), text).getOutline();
    // the shape returned is located at the left side of the baseline, this means we need to re-align it to the top left corner. We also want to set it the the center of the screen while we are there
    AffineTransform transform = AffineTransform.getTranslateInstance(
                -outline.getBounds().getX() + getWidth()/2 - outline.getBounds().width / 2, 
                -outline.getBounds().getY() + getHeight()/2 - outline.getBounds().height / 2);
    outline = transform.createTransformedShape(outline);
    g2d.fill(outline);
}

comme je l'ai déjà dit, essayez d'utiliser les paramètres de police, mais si tout le reste échoue, essayez ceci. la méthode.

5
répondu ug_ 2017-05-23 12:25:02