java-topology/test/jdk/java/awt/font/TextLayout/RotFontBoundsTest.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

257 lines
9.4 KiB
Java

/*
* Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import static javax.swing.BorderFactory.createEmptyBorder;
/*
* @test
* @bug 4650997
* @summary rotate a TextLayout and verify that the bounds are correct
* @library /java/awt/regtesthelpers
* @build PassFailJFrame
* @run main/manual RotFontBoundsTest
*/
public final class RotFontBoundsTest {
private static final String TEXT = ".This is a STRINg.";
private static final String INSTRUCTIONS =
"A string \u201C" + TEXT + "\u201D is drawn at eight different "
+ "angles, and eight boxes that surround the bounds of the text "
+ "layouts (give or take a pixel) are drawn in red. The boxes "
+ "are always composed of horizontal and vertical lines \u2014 "
+ "they are not rotated.\n"
+ "\n"
+ "By default, all the rotations are displayed. Select or clear "
+ "a check box with an angle to show or hide a particular "
+ "rotation. Click \"Select All\" or \"Clear All\" to show all "
+ "the rotations or to hide them.\n"
+ "\n"
+ "Click the Pass button if each box encloses its corresponding "
+ "text layout.\n"
+ "Otherwise, click Screenshot to save a screenshot for failure "
+ "analysis and then click Fail.";
private static boolean verbose;
public static void main(String[] args) throws Exception {
verbose = (args.length > 0 && args[0].equalsIgnoreCase("verbose"));
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
PassFailJFrame.builder()
.instructions(INSTRUCTIONS)
.rows(20)
.columns(50)
.testTimeOut(15)
.screenCapture()
.testUI(RotFontBoundsTest::createUI)
.build()
.awaitAndCheck();
}
private static final int ROTATIONS = 8;
private static JComponent createUI() {
final RotatedTextBounds rotatedText = new RotatedTextBounds();
final JPanel checkBoxes = new JPanel(new FlowLayout(FlowLayout.CENTER,
4, 4));
checkBoxes.setBorder(createEmptyBorder(0, 8, 8, 8));
for (int i = 0; i < ROTATIONS; i++) {
checkBoxes.add(new JCheckBox(new SelectRotationAction(i, rotatedText)));
}
JButton selectAll = new JButton("Select All");
selectAll.addActionListener(
e -> selectAllCheckBoxes(checkBoxes.getComponents(), true));
selectAll.setMnemonic('S');
JButton clearAll = new JButton("Clear All");
clearAll.addActionListener(
e -> selectAllCheckBoxes(checkBoxes.getComponents(), false));
clearAll.setMnemonic('C');
Box controls = Box.createHorizontalBox();
controls.add(new JLabel("Visible Rotations:"));
controls.add(Box.createHorizontalGlue());
controls.add(selectAll);
controls.add(Box.createHorizontalStrut(4));
controls.add(clearAll);
controls.setBorder(createEmptyBorder(8, 8, 0, 8));
Box controlPanel = Box.createVerticalBox();
controlPanel.add(controls);
controlPanel.add(checkBoxes);
Box javaVersion = Box.createHorizontalBox();
javaVersion.setBorder(createEmptyBorder(8, 8, 8, 8));
javaVersion.add(new JLabel("Java version: "
+ System.getProperty("java.runtime.version")));
javaVersion.add(Box.createHorizontalGlue());
Box main = Box.createVerticalBox();
main.setName("Rotated TextLayout Test");
main.add(controlPanel);
main.add(rotatedText);
main.add(javaVersion);
return main;
}
private static final class RotatedTextBounds extends JComponent {
private final Font font = new Font(Font.DIALOG, Font.PLAIN, 24);
private final boolean[] rotationVisible = new boolean[ROTATIONS];
private RotatedTextBounds() {
setBackground(Color.WHITE);
setPreferredSize(new Dimension(400, 400));
Arrays.fill(rotationVisible, true);
}
public void setRotationVisible(int rotation, boolean visible) {
rotationVisible[rotation] = visible;
repaint();
}
// Counts the number of paints
private int counter = 0;
@Override
public void paintComponent(Graphics _g) {
Graphics2D g = (Graphics2D) _g;
Dimension d = getSize();
g.setColor(getBackground());
g.fillRect(0, 0, d.width, d.height);
counter++;
int x = d.width / 2;
int y = d.height / 2;
FontRenderContext frc = g.getFontRenderContext();
for (int i = 0; i < ROTATIONS; i++) {
if (!rotationVisible[i]) {
continue;
}
double angle = -Math.PI / 4.0 * i;
AffineTransform flip = AffineTransform.getRotateInstance(angle);
Font flippedFont = font.deriveFont(flip);
TextLayout tl = new TextLayout(TEXT, flippedFont, frc);
Rectangle2D bb = tl.getBounds();
g.setPaint(Color.BLACK);
tl.draw(g, x, y);
g.setPaint(Color.RED);
g.drawRect(x + (int) bb.getX(), y + (int) bb.getY(),
(int) bb.getWidth(), (int) bb.getHeight());
if (verbose) {
if (counter == 1) {
printDetails(angle, tl);
} else if (i == 0) {
System.out.println("Paint, counter=" + counter);
}
}
}
}
private static void printDetails(double angle, TextLayout tl) {
System.out.println("Angle: " + angle);
System.out.println("getAscent: " + tl.getAscent());
System.out.println("getAdvance: " + tl.getAdvance());
System.out.println("getBaseline: " + tl.getBaseline());
System.out.println("getBounds: " + tl.getBounds());
System.out.println("getDescent: " + tl.getDescent());
System.out.println("getLeading: " + tl.getLeading());
System.out.println("getVisibleAdvance: " + tl.getVisibleAdvance());
System.out.println(".");
}
}
private static final class SelectRotationAction
extends AbstractAction
implements PropertyChangeListener {
private final int rotation;
private final RotatedTextBounds rotatedText;
private SelectRotationAction(int rotation,
RotatedTextBounds rotatedText) {
super(rotation * (360 / ROTATIONS) + "\u00B0");
this.rotation = rotation;
this.rotatedText = rotatedText;
putValue(SELECTED_KEY, true);
addPropertyChangeListener(this);
}
private void updateRotationVisible() {
rotatedText.setRotationVisible(rotation,
(Boolean) getValue(SELECTED_KEY));
}
@Override
public void actionPerformed(ActionEvent e) {
updateRotationVisible();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(SELECTED_KEY)) {
updateRotationVisible();
}
}
}
private static void selectAllCheckBoxes(Component[] checkBoxes,
boolean visible) {
Arrays.stream(checkBoxes)
.forEach(c -> ((JCheckBox) c).setSelected(visible));
}
}