| /* | |
| * Copyright (c) 1999, 2024, 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.Dimension; | |
| import java.awt.Font; | |
| import java.awt.Frame; | |
| import java.awt.Graphics; | |
| import java.awt.Graphics2D; | |
| import java.awt.Insets; | |
| import java.awt.Panel; | |
| import java.awt.Rectangle; | |
| import java.awt.Shape; | |
| import java.awt.font.FontRenderContext; | |
| import java.awt.font.LineBreakMeasurer; | |
| import java.awt.font.TextAttribute; | |
| import java.awt.font.TextLayout; | |
| import java.awt.geom.Rectangle2D; | |
| import java.text.AttributedCharacterIterator; | |
| import java.text.AttributedString; | |
| /* | |
| * @test | |
| * @bug 4211728 4178140 8145542 | |
| * @summary Justify several lines of text and verify that the lines are the same | |
| length and cursor positions are correct. | |
| Bug 4211728: TextLayout.draw() draws characters at wrong position. | |
| Bug 4178140: TextLayout does not justify. | |
| * @library /java/awt/regtesthelpers | |
| * @build PassFailJFrame | |
| * @run main/manual TestJustification | |
| */ | |
| public class TestJustification { | |
| private static final String INSTRUCTIONS = """ | |
| Five lines of text should appear, all justified to the same width, | |
| followed by a sixth line containing only roman characters and | |
| no spaces which is not justified, and instead is centered. | |
| Carets should appear between all characters. | |
| PASS the test if this is true, else press FAIL. | |
| """; | |
| public static void main(String[] args) throws Exception { | |
| PassFailJFrame.builder() | |
| .title("Test Instructions") | |
| .instructions(INSTRUCTIONS) | |
| .rows((int) INSTRUCTIONS.lines().count() + 2) | |
| .columns(35) | |
| .testUI(TestJustification::createUI) | |
| .build() | |
| .awaitAndCheck(); | |
| } | |
| private static Frame createUI() { | |
| Frame frame= new Frame("Test Text Justification"); | |
| JustificationPanel panel = new JustificationPanel("Bitstream Cyberbit"); | |
| frame.add(panel); | |
| frame.add("Center", panel); | |
| frame.setSize(500, 450); | |
| return frame; | |
| } | |
| static class JustificationPanel extends Panel { | |
| TextLayout[] layouts; | |
| String fontname; | |
| float height; | |
| float oldfsize; | |
| AttributedCharacterIterator lineText; | |
| TextLayout[] lines; | |
| int linecount; | |
| float oldwidth; | |
| JustificationPanel(String fontname) { | |
| this.fontname = fontname; | |
| } | |
| private static final String[] texts = { | |
| "This is an english Highlighting demo.", "Highlighting", | |
| "This is an arabic \u0627\u0628\u062a\u062c \u062e\u0644\u0627\u062e demo.", "arabic \u0627\u0628\u062a\u062c", | |
| "This is a hebrew \u05d0\u05d1\u05d2 \u05d3\u05d4\u05d5 demo.", "hebrew \u05d0\u05d1\u05d2", | |
| "This is a cjk \u4e00\u4e01\u4e02\uac00\uac01\uc4fa\uf900\uf901\uf902 demo.", "cjk", | |
| "NoSpaceCJK:\u4e00\u4e01\u4e02and\uac00\uac01\uc4faand\uf900\uf901\uf902", "No", | |
| "NoSpaceRoman", "Space" | |
| }; | |
| public void paint(Graphics g) { | |
| Graphics2D g2d = (Graphics2D)g; | |
| Dimension d = getSize(); | |
| Insets insets = getInsets(); | |
| float w = d.width - insets.left - insets.right; | |
| float h = d.height - insets.top - insets.bottom; | |
| int fsize = (int)w/25; | |
| FontRenderContext frc = g2d.getFontRenderContext(); | |
| if (layouts == null || fsize != oldfsize) { | |
| oldfsize = fsize; | |
| Font f0 = new Font(fontname, Font.PLAIN, fsize); | |
| Font f1 = new Font(fontname, Font.ITALIC, (int)(fsize * 1.5)); | |
| if (layouts == null) { | |
| layouts = new TextLayout[texts.length / 2]; | |
| } | |
| height = 0; | |
| for (int i = 0; i < layouts.length; ++i) { | |
| String text = texts[i*2]; | |
| String target = texts[i*2+1]; | |
| AttributedString astr = new AttributedString(text); | |
| astr.addAttribute(TextAttribute.FONT, f0, 0, text.length()); | |
| int start = text.indexOf(target); | |
| int limit = start + target.length(); | |
| astr.addAttribute(TextAttribute.FONT, f1, start, limit); | |
| TextLayout layout = new TextLayout(astr.getIterator(), frc); | |
| layout = layout.getJustifiedLayout(w - 20); | |
| layouts[i] = layout; | |
| height += layout.getAscent() + layout.getDescent() + layout.getLeading(); | |
| } | |
| } | |
| g2d.setColor(Color.white); | |
| g2d.fill(new Rectangle.Float(insets.left, insets.top, w, h)); | |
| float basey = 20; | |
| for (TextLayout layout : layouts) { | |
| float la = layout.getAscent(); | |
| float ld = layout.getDescent(); | |
| float ll = layout.getLeading(); | |
| float lw = layout.getAdvance(); | |
| float lh = la + ld + ll; | |
| float lx = (w - lw) / 2f; | |
| float ly = basey + layout.getAscent(); | |
| g2d.setColor(Color.black); | |
| g2d.translate(insets.left + lx, insets.top + ly); | |
| Rectangle2D bounds = new Rectangle2D.Float(0, -la, lw, lh); | |
| g2d.draw(bounds); | |
| layout.draw(g2d, 0, 0); | |
| g2d.setColor(Color.red); | |
| for (int j = 0, e = layout.getCharacterCount(); j <= e; ++j) { | |
| Shape[] carets = layout.getCaretShapes(j, bounds); | |
| g2d.draw(carets[0]); | |
| } | |
| g2d.translate(-insets.left - lx, -insets.top - ly); | |
| basey += layout.getAscent() + layout.getDescent() + layout.getLeading(); | |
| } | |
| // add LineBreakMeasurer-generated layouts | |
| if (lineText == null) { | |
| String text = "This is a long line of text that should be broken across multiple " | |
| + "lines and then justified to fit the break width. This test should pass if " | |
| + "these lines are justified to the same width, and fail otherwise. It should " | |
| + "also format the hebrew (\u05d0\u05d1\u05d2 \u05d3\u05d4\u05d5) and arabic " | |
| + "(\u0627\u0628\u062a\u062c \u062e\u0644\u0627\u062e) and CJK " | |
| + "(\u4e00\u4e01\u4e02\uac00\uac01\uc4fa\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7" | |
| + "\u67b8\u67b9) text correctly."; | |
| Float regular = 16.0F; | |
| Float big = 24.0F; | |
| AttributedString astr = new AttributedString(text); | |
| astr.addAttribute(TextAttribute.SIZE, regular, 0, text.length()); | |
| astr.addAttribute(TextAttribute.FAMILY, fontname, 0, text.length()); | |
| int ix = text.indexOf("broken"); | |
| astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6); | |
| ix = text.indexOf("hebrew"); | |
| astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6); | |
| ix = text.indexOf("arabic"); | |
| astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6); | |
| ix = text.indexOf("CJK"); | |
| astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 3); | |
| lineText = astr.getIterator(); | |
| } | |
| float width = w - 20; | |
| if (lines == null || width != oldwidth) { | |
| oldwidth = width; | |
| lines = new TextLayout[10]; | |
| linecount = 0; | |
| LineBreakMeasurer measurer = new LineBreakMeasurer(lineText, frc); | |
| for (;;) { | |
| TextLayout layout = measurer.nextLayout(width); | |
| if (layout == null) { | |
| break; | |
| } | |
| // justify all but last line | |
| if (linecount > 0) { | |
| lines[linecount - 1] = lines[linecount - 1].getJustifiedLayout(width); | |
| } | |
| if (linecount == lines.length) { | |
| TextLayout[] nlines = new TextLayout[lines.length * 2]; | |
| System.arraycopy(lines, 0, nlines, 0, lines.length); | |
| lines = nlines; | |
| } | |
| lines[linecount++] = layout; | |
| } | |
| } | |
| float basex = insets.left + 10; | |
| basey += 10; | |
| g2d.setColor(Color.black); | |
| for (int i = 0; i < linecount; ++i) { | |
| TextLayout layout = lines[i]; | |
| basey += layout.getAscent(); | |
| float adv = layout.getAdvance(); | |
| float dx = layout.isLeftToRight() ? 0 : width - adv; | |
| layout.draw(g2d, basex + dx, basey); | |
| basey += layout.getDescent() + layout.getLeading(); | |
| } | |
| } | |
| } | |
| } |