0

I'm having difficulty being able to vertical center textLayouts on a Graphics2d object. I'm using:

((intGraphics2dHeight / 2) + textLayout.getBounds().getHeight()) - textLayout.getDescent()

The result is the text is bouncing up and down.

Test strings are:

oaoaoa g oaoaoa
WWWWWW W WWWWWW
Prryrr P Prryrr

I'm probably not using texLayout correctly, but don't know how to fix it.

John Smith
  • 3,493
  • 3
  • 25
  • 52
  • [Example](https://stackoverflow.com/questions/49593022/advanced-text-in-image-in-java/49593642#49593642), [example](https://stackoverflow.com/questions/16729095/assigning-a-image-to-a-string/16729305#16729305), [example](https://stackoverflow.com/questions/23729944/java-how-to-visually-center-a-specific-string-not-just-a-font-in-a-rectangle/23730104#23730104), [example](https://stackoverflow.com/questions/29247833/in-java-graphics2d-how-can-text-be-center-aligned-on-a-rectangle/29247946#29247946) – MadProgrammer Sep 24 '21 at 01:25
  • ... [example](https://stackoverflow.com/questions/34690321/java-graphics-random-text/34691463#34691463) – MadProgrammer Sep 24 '21 at 01:26
  • @MadProgrammer - Thanks for those examples. I had a look through them. My problem is that these particular Strings don't center vertically with any of them. I modified the OP slightly. – John Smith Sep 24 '21 at 01:44

1 Answers1

1

I'd suggest having a look at Working with Text APIs

A font represents the information necessary to render text

enter image description here

Simple

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Arrays;
import java.util.StringJoiner;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }

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

        private String[] texts = new String[]{
            "oaoaoa g oaoaoa",
            "WWWWWW W WWWWWW",
            "Prryrr P Prryrr"
        };

        @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);

            g2d.setColor(Color.BLACK);

            FontMetrics fm = g2d.getFontMetrics();

            int width = 0;
            for (String text : texts) {
                width += fm.stringWidth(text);
            }

            int xPos = (getWidth() - width) / 2;
            int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();

            int offset = 0;
            for (String text : texts) {
                g2d.drawString(text, xPos + offset, yPos);
                offset += fm.stringWidth(text);
            }

            g2d.dispose();
        }

    }
}

Simple

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Arrays;
import java.util.StringJoiner;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }

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

        private String[] texts = new String[]{
            "oaoaoa g oaoaoa",
            "WWWWWW W WWWWWW",
            "Prryrr P Prryrr"
        };

        @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);

            g2d.setColor(Color.BLACK);

            FontMetrics fm = g2d.getFontMetrics();

            int width = 0;
            for (String text : texts) {
                width += fm.stringWidth(text);
            }

            int xPos = (getWidth() - width) / 2;
            int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();

            int offset = 0;
            for (String text : texts) {
                g2d.drawString(text, xPos + offset, yPos);
                offset += fm.stringWidth(text);
            }

            g2d.dispose();
        }

    }
}

So, not seeing an issue, but since I have no context to your issue, I'm just guessing

Expanded Example using TextLayout

TextLayout

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.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
        }

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

        private String[] texts = new String[]{
            "oaoaoa g oaoaoa",
            "WWWWWW W WWWWWW",
            "Prryrr P Prryrr"
        };

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

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

            g2d.setColor(Color.BLACK);

            Font font = g2d.getFont();

            FontRenderContext frc = g2d.getFontRenderContext();
            Map<String, GlyphVector> vectors = new HashMap<>();
            double width = 0;
            for (String text : texts) {
                GlyphVector gv = font.createGlyphVector(frc, text);
                vectors.put(text, gv);
                width += gv.getVisualBounds().getWidth();
            }

            int xPos = (int) ((getWidth() - width) / 2.0);

            for (String text : texts) {
                xPos = render(g2d, text, xPos);
            }

            g2d.dispose();
        }

        protected int render(Graphics2D g2d, String text, int xOffset) {
            Font font = g2d.getFont();
            FontRenderContext frc = g2d.getFontRenderContext();

            int xPos = xOffset;
            for (char character : text.toCharArray()) {
                String value = Character.toString(character);
                // Create an layout of the text
                TextLayout tl = new TextLayout(value, font, frc);
                // Generate a shape of the layout
                Shape shape = tl.getOutline(null);
                // Align the shape to the center
                Rectangle rect = shape.getBounds();

                Graphics2D gCopy = (Graphics2D) g2d.create();
                int yPos = (getHeight() / 2) + (rect.height / 2);
                gCopy.translate(xPos, yPos);
                gCopy.fill(shape);
                gCopy.dispose();

                xPos += (int) (rect.getWidth());
            }
            return xPos;
        }

    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • oaoaoa g oaoaoa is not centered. The g is too low. But you are right on the money for the code. Mine is very similar – John Smith Sep 24 '21 at 01:47
  • @JohnSmith It is, based on the requirements of the font - see [Working with Text APIs](https://docs.oracle.com/javase/tutorial/2d/text/index.html) – MadProgrammer Sep 24 '21 at 01:48
  • I did have a look at that. I not sure how to get the lowest "descender" and highest "ascender". getDescent() seems to return non zero for WWW W WWWW which makes no sense to me. I appreciate your time with this! – John Smith Sep 24 '21 at 01:55
  • A letter in a font occupies a "box", so the "height" is generally the same and the "letter" is then rendered within that box, based on things like the baseline, ascent and decent properties - this is basic font – MadProgrammer Sep 24 '21 at 02:01
  • I put your code in and moved yPos into the for loop and changed: int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent() - fm.getDescent(); Now, the WWWWWW W WWWWW is above the center. The other two are centered as I want. This is where I'm confused. Why is that String returning a descender? – John Smith Sep 24 '21 at 02:35
  • Ok, It seems that this is only returning for the font itself. Is there a way to get the actual height of the String and not the font? – John Smith Sep 24 '21 at 02:39
  • @JohnSmith There the same thing. A font describes how a string is rendered, but look at the `TextLayout` example – MadProgrammer Sep 24 '21 at 02:40
  • Yes. I'm having the same issue. It seem that textLayout.getDescent() and textLayout.getAscent() are only returning the font property and it doesn't pertain to the actual string itself. What I need is a way determine the true height from descension to ascension of the actual string. – John Smith Sep 24 '21 at 02:51
  • Yes, look at the `TextLayout` example – MadProgrammer Sep 24 '21 at 02:59
  • 1
    @JohnSmith: if you "correctly" center text then a `g` **should** be a little lower than perfectly centered, because the lower part of the `g` goes below the line. You can't get a stable centering **and** also not accept that. Put differently: the vertical position should only depend on the font properties and the number of lines, not on the actual characters used. Only the horizontal alignment should take actual characters into account. – Joachim Sauer Sep 24 '21 at 07:59