java-topology/test/jdk/java/awt/font/TextLayout/TextLayoutConstructorTest.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

316 lines
14 KiB
Java

/*
* Copyright (c) 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.
*/
/*
* @test
* @bug 4138921
* @summary Confirm constructor behavior for various edge cases.
*/
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
public class TextLayoutConstructorTest {
public static void main(String[] args) throws Exception {
testFontConstructor();
testMapConstructor();
testIteratorConstructor();
}
private static void testFontConstructor() {
// new TextLayout(String, Font, FontRenderContext)
Font font = new Font(Font.DIALOG, Font.PLAIN, 20);
FontRenderContext frc = new FontRenderContext(null, true, true);
assertThrows(() -> new TextLayout(null, font, frc),
IllegalArgumentException.class,
"Null string passed to TextLayout constructor.");
assertThrows(() -> new TextLayout("test", (Font) null, frc),
IllegalArgumentException.class,
"Null font passed to TextLayout constructor.");
assertThrows(() -> new TextLayout("test", font, null),
IllegalArgumentException.class,
"Null font render context passed to TextLayout constructor.");
Function< String, TextLayout > creator = (s) -> new TextLayout(s, font, frc);
assertEmptyTextLayoutBehavior(creator);
}
private static void testMapConstructor() {
// new TextLayout(String, Map, FontRenderContext)
Map< TextAttribute, Object > attributes = Map.of(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
FontRenderContext frc = new FontRenderContext(null, true, true);
assertThrows(() -> new TextLayout(null, attributes, frc),
IllegalArgumentException.class,
"Null string passed to TextLayout constructor.");
assertThrows(() -> new TextLayout("test", (Map) null, frc),
IllegalArgumentException.class,
"Null map passed to TextLayout constructor.");
assertThrows(() -> new TextLayout("test", attributes, null),
IllegalArgumentException.class,
"Null font render context passed to TextLayout constructor.");
Function< String, TextLayout > creator = (s) -> new TextLayout(s, attributes, frc);
assertEmptyTextLayoutBehavior(creator);
}
private static void testIteratorConstructor() {
// new TextLayout(AttributedCharacterIterator, FontRenderContext)
Map< TextAttribute, Object > attributes = Map.of();
FontRenderContext frc = new FontRenderContext(null, true, true);
assertThrows(() -> new TextLayout(null, frc),
IllegalArgumentException.class,
"Null iterator passed to TextLayout constructor.");
AttributedCharacterIterator it1 = new AttributedString("test", attributes).getIterator();
assertThrows(() -> new TextLayout(it1, null),
IllegalArgumentException.class,
"Null font render context passed to TextLayout constructor.");
Function< String, TextLayout > creator = (s) -> {
AttributedCharacterIterator it2 = new AttributedString(s, attributes).getIterator();
return new TextLayout(it2, frc);
};
assertEmptyTextLayoutBehavior(creator);
}
private static void assertEmptyTextLayoutBehavior(Function< String, TextLayout > creator) {
TextLayout tl = creator.apply("");
TextLayout ref = creator.apply(" "); // space
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle zero = new Rectangle(0, 0, 0, 0);
Rectangle2D.Float zero2D = new Rectangle2D.Float(0, 0, 0, 0);
Rectangle2D.Float oneTwo = new Rectangle2D.Float(1, 2, 0, 0);
Rectangle2D.Float kilo = new Rectangle2D.Float(0, 0, 1000, 1000);
AffineTransform identity = new AffineTransform();
TextLayout.CaretPolicy policy = new TextLayout.CaretPolicy();
TextHitInfo start = TextHitInfo.trailing(-1);
TextHitInfo end = TextHitInfo.leading(0);
assertEqual(0, tl.getJustifiedLayout(100).getAdvance(), "justified advance");
assertEqual(0, tl.getBaseline(), "baseline");
float[] offsets = tl.getBaselineOffsets();
float[] refOffsets = ref.getBaselineOffsets();
assertEqual(3, offsets.length, "baseline offsets");
assertEqual(refOffsets[0], offsets[0], "baseline offset 1");
assertEqual(refOffsets[1], offsets[1], "baseline offset 2");
assertEqual(refOffsets[2], offsets[2], "baseline offset 3");
assertEqual(0, tl.getAdvance(), "advance");
assertEqual(0, tl.getVisibleAdvance(), "visible advance");
assertEqual(ref.getAscent(), tl.getAscent(), "ascent");
assertEqual(ref.getDescent(), tl.getDescent(), "descent");
assertEqual(ref.getLeading(), tl.getLeading(), "leading");
assertEqual(zero2D, tl.getBounds(), "bounds");
assertEqual(zero2D, tl.getPixelBounds(frc, 0, 0), "pixel bounds 1");
assertEqual(oneTwo, tl.getPixelBounds(frc, 1, 2), "pixel bounds 2");
assertEqual(true, tl.isLeftToRight(), "left to right");
assertEqual(false, tl.isVertical(), "is vertical");
assertEqual(0, tl.getCharacterCount(), "character count");
float[] caretInfo = tl.getCaretInfo(start, kilo);
float[] refCaretInfo = ref.getCaretInfo(start, kilo);
assertEqual(6, caretInfo.length, "caret info length 1");
assertEqual(refCaretInfo[0], caretInfo[0], "first caret info 1");
assertEqual(refCaretInfo[1], caretInfo[1], "second caret info 1");
assertEqual(refCaretInfo[2], caretInfo[2], "third caret info 1");
assertEqual(refCaretInfo[3], caretInfo[3], "fourth caret info 1");
assertEqual(refCaretInfo[4], caretInfo[4], "fifth caret info 1");
assertEqual(refCaretInfo[5], caretInfo[5], "sixth caret info 1");
float[] caretInfo2 = tl.getCaretInfo(start);
float[] refCaretInfo2 = ref.getCaretInfo(start);
assertEqual(6, caretInfo2.length, "caret info length 2");
assertEqual(refCaretInfo2[0], caretInfo2[0], "first caret info 2");
assertEqual(refCaretInfo2[1], caretInfo2[1], "second caret info 2");
assertEqual(refCaretInfo2[2], caretInfo2[2], "third caret info 2");
assertEqual(refCaretInfo2[3], caretInfo2[3], "fourth caret info 2");
assertEqual(refCaretInfo2[4], caretInfo2[4], "fifth caret info 2");
assertEqual(refCaretInfo2[5], caretInfo2[5], "sixth caret info 2");
assertEqual(null, tl.getNextRightHit(start), "next right hit 1");
assertEqual(null, tl.getNextRightHit(end), "next right hit 2");
assertEqual(null, tl.getNextRightHit(0, policy), "next right hit 3");
assertEqual(null, tl.getNextRightHit(0), "next right hit 4");
assertEqual(null, tl.getNextLeftHit(start), "next left hit 1");
assertEqual(null, tl.getNextLeftHit(end), "next left hit 2");
assertEqual(null, tl.getNextLeftHit(0, policy), "next left hit 3");
assertEqual(null, tl.getNextLeftHit(0), "next left hit 4");
assertEqual(end, tl.getVisualOtherHit(start), "visual other hit");
Shape caretShape = tl.getCaretShape(start, kilo);
Shape refCaretShape = ref.getCaretShape(start, kilo);
assertEqual(refCaretShape.getBounds(), caretShape.getBounds(), "caret shape 1");
Shape caretShape2 = tl.getCaretShape(start);
Shape refCaretShape2 = ref.getCaretShape(start);
assertEqual(refCaretShape2.getBounds(), caretShape2.getBounds(), "caret shape 2");
assertEqual(0, tl.getCharacterLevel(0), "character level");
Shape[] caretShapes = tl.getCaretShapes(0, kilo, policy);
Shape[] refCaretShapes = ref.getCaretShapes(0, kilo, policy);
assertEqual(2, caretShapes.length, "caret shapes length 1");
assertEqual(refCaretShapes[0].getBounds(), caretShapes[0].getBounds(), "caret shapes strong 1");
assertEqual(refCaretShapes[1], caretShapes[1], "caret shapes weak 1");
assertEqual(null, caretShapes[1], "caret shapes weak 1");
Shape[] caretShapes2 = tl.getCaretShapes(0, kilo);
Shape[] refCaretShapes2 = ref.getCaretShapes(0, kilo);
assertEqual(2, caretShapes2.length, "caret shapes length 2");
assertEqual(refCaretShapes2[0].getBounds(), caretShapes2[0].getBounds(), "caret shapes strong 2");
assertEqual(refCaretShapes2[1], caretShapes2[1], "caret shapes weak 2");
assertEqual(null, caretShapes2[1], "caret shapes weak 2");
Shape[] caretShapes3 = tl.getCaretShapes(0);
Shape[] refCaretShapes3 = ref.getCaretShapes(0);
assertEqual(2, caretShapes3.length, "caret shapes length 3");
assertEqual(refCaretShapes3[0].getBounds(), caretShapes3[0].getBounds(), "caret shapes strong 3");
assertEqual(refCaretShapes3[1], caretShapes3[1], "caret shapes weak 3");
assertEqual(null, caretShapes3[1], "caret shapes weak 3");
assertEqual(0, tl.getLogicalRangesForVisualSelection(start, start).length, "logical ranges for visual selection");
assertEqual(zero2D, tl.getVisualHighlightShape(start, start, kilo).getBounds(), "visual highlight shape 1");
assertEqual(zero2D, tl.getVisualHighlightShape(start, start).getBounds(), "visual highlight shape 2");
assertEqual(zero, tl.getLogicalHighlightShape(0, 0, kilo).getBounds(), "logical highlight shape 1");
assertEqual(zero, tl.getLogicalHighlightShape(0, 0).getBounds(), "logical highlight shape 2");
assertEqual(zero, tl.getBlackBoxBounds(0, 0).getBounds(), "black box bounds");
TextHitInfo hit = tl.hitTestChar(0, 0);
assertEqual(-1, hit.getCharIndex(), "hit test char index 1");
assertEqual(false, hit.isLeadingEdge(), "hit test leading edge 1");
TextHitInfo hit2 = tl.hitTestChar(0, 0, kilo);
assertEqual(-1, hit2.getCharIndex(), "hit test char index 2");
assertEqual(false, hit2.isLeadingEdge(), "hit test leading edge 2");
assertEqual(false, tl.equals(creator.apply("")), "equals");
assertEqual(false, tl.toString().isEmpty(), "to string");
assertDoesNotDraw(tl);
assertEqual(zero2D, tl.getOutline(identity).getBounds(), "outline");
assertEqual(null, tl.getLayoutPath(), "layout path");
Point2D.Float point = new Point2D.Float(7, 7);
tl.hitToPoint(start, point);
assertEqual(0, point.x, "hit to point x");
assertEqual(0, point.y, "hit to point y");
}
private static void assertEqual(int expected, int actual, String name) {
if (expected != actual) {
throw new RuntimeException("Expected " + name + " = " + expected + ", but got " + actual);
}
}
private static void assertEqual(float expected, float actual, String name) {
if (expected != actual) {
throw new RuntimeException("Expected " + name + " = " + expected + ", but got " + actual);
}
}
private static void assertEqual(boolean expected, boolean actual, String name) {
if (expected != actual) {
throw new RuntimeException("Expected " + name + " = " + expected + ", but got " + actual);
}
}
private static void assertEqual(Object expected, Object actual, String name) {
if (!Objects.equals(expected, actual)) {
throw new RuntimeException("Expected " + name + " = " + expected + ", but got " + actual);
}
}
private static void assertThrows(Runnable r, Class< ? > type, String message) {
Class< ? > actualType;
String actualMessage;
Exception actualException;
try {
r.run();
actualType = null;
actualMessage = null;
actualException = null;
} catch (Exception e) {
actualType = e.getClass();
actualMessage = e.getMessage();
actualException = e;
}
if (!Objects.equals(type, actualType)) {
throw new RuntimeException(type + " != " + actualType, actualException);
}
if (!Objects.equals(message, actualMessage)) {
throw new RuntimeException(message + " != " + actualMessage, actualException);
}
}
private static void assertDoesNotDraw(TextLayout layout) {
int w = 200;
int h = 200;
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);
Graphics2D g2d = image.createGraphics();
int expected = image.getRGB(0, 0);
layout.draw(g2d, w / 2f, h / 2f); // should not actually draw anything
int[] rowPixels = new int[w];
for (int y = 0; y < h; y++) {
image.getRGB(0, y, w, 1, rowPixels, 0, w);
for (int x = 0; x < w; x++) {
if (rowPixels[x] != expected) {
throw new RuntimeException(
"pixel (" + x + ", " + y +"): " + expected + " != " + rowPixels[x]);
}
}
}
}
}