8165943: LineBreakMeasurer does not measure correctly if TextAttribute.TRACKING is set.
Co-authored-by: Jason Fordham <jclf@azul.com> Reviewed-by: prr
This commit is contained in:
parent
39344840c7
commit
8edb98df3d
src/java.desktop/share/classes/sun/font
test/jdk/java/awt/font/LineBreakMeasurer
@ -846,6 +846,23 @@ public final class AttributeValues implements Cloneable {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static float getTracking(Map<?, ?> map) {
|
||||
if (map != null) {
|
||||
AttributeValues av = null;
|
||||
if (map instanceof AttributeMap &&
|
||||
((AttributeMap) map).getValues() != null) {
|
||||
av = ((AttributeMap)map).getValues();
|
||||
} else if (map.get(TextAttribute.TRACKING) != null) {
|
||||
av = AttributeValues.fromMap((Map<Attribute, ?>)map);
|
||||
}
|
||||
if (av != null) {
|
||||
return av.tracking;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void updateDerivedTransforms() {
|
||||
// this also updates the mask for the baseline transform
|
||||
if (transform == null) {
|
||||
|
@ -71,6 +71,8 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
|
||||
StandardGlyphVector gv;
|
||||
float[] charinfo;
|
||||
|
||||
float advTracking;
|
||||
|
||||
/**
|
||||
* Create from a TextSource.
|
||||
*/
|
||||
@ -110,6 +112,8 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
|
||||
source.getStart() + source.getLength(), source.getFRC());
|
||||
cm = CoreMetrics.get(lm);
|
||||
}
|
||||
|
||||
advTracking = font.getSize() * AttributeValues.getTracking(atts);
|
||||
}
|
||||
|
||||
|
||||
@ -378,10 +382,10 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
|
||||
validate(index);
|
||||
float[] charinfo = getCharinfo();
|
||||
int idx = l2v(index) * numvals + advx;
|
||||
if (charinfo == null || idx >= charinfo.length) {
|
||||
if (charinfo == null || idx >= charinfo.length || charinfo[idx] == 0) {
|
||||
return 0f;
|
||||
} else {
|
||||
return charinfo[idx];
|
||||
return charinfo[idx] + advTracking;
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,16 +481,25 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
|
||||
}
|
||||
|
||||
public int getLineBreakIndex(int start, float width) {
|
||||
final float epsilon = 0.005f;
|
||||
|
||||
float[] charinfo = getCharinfo();
|
||||
int length = source.getLength();
|
||||
|
||||
if (advTracking > 0) {
|
||||
width += advTracking;
|
||||
}
|
||||
|
||||
--start;
|
||||
while (width >= 0 && ++start < length) {
|
||||
while (width >= -epsilon && ++start < length) {
|
||||
int cidx = l2v(start) * numvals + advx;
|
||||
if (cidx >= charinfo.length) {
|
||||
break; // layout bailed for some reason
|
||||
}
|
||||
float adv = charinfo[cidx];
|
||||
width -= adv;
|
||||
if (adv != 0) {
|
||||
width -= adv + advTracking;
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
@ -502,7 +515,10 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
|
||||
if (cidx >= charinfo.length) {
|
||||
break; // layout bailed for some reason
|
||||
}
|
||||
a += charinfo[cidx];
|
||||
float adv = charinfo[cidx];
|
||||
if (adv != 0) {
|
||||
a += adv + advTracking;
|
||||
}
|
||||
}
|
||||
|
||||
return a;
|
||||
|
@ -197,21 +197,22 @@ public class StandardGlyphVector extends GlyphVector {
|
||||
|
||||
// how do we know its a base glyph
|
||||
// for now, it is if the natural advance of the glyph is non-zero
|
||||
Font2D f2d = FontUtilities.getFont2D(font);
|
||||
FontStrike strike = f2d.getStrike(font, frc);
|
||||
|
||||
float[] deltas = { trackPt.x, trackPt.y };
|
||||
for (int j = 0; j < deltas.length; ++j) {
|
||||
float inc = deltas[j];
|
||||
float prevPos = 0;
|
||||
if (inc != 0) {
|
||||
float delta = 0;
|
||||
for (int i = j, n = 0; n < glyphs.length; i += 2) {
|
||||
if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test
|
||||
for (int i = j; i < positions.length; i += 2) {
|
||||
if (i == j || prevPos != positions[i]) {
|
||||
prevPos = positions[i];
|
||||
positions[i] += delta;
|
||||
delta += inc;
|
||||
} else if (prevPos == positions[i]) {
|
||||
positions[i] = positions[i - 2];
|
||||
}
|
||||
}
|
||||
positions[positions.length-2+j] += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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 8165943
|
||||
* @summary LineBreakMeasurer does not measure correctly if TextAttribute.TRACKING is set
|
||||
* @library ../../regtesthelpers
|
||||
* @build PassFailJFrame
|
||||
* @run main/manual LineBreakWithTracking
|
||||
*/
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.LineBreakMeasurer;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.text.AttributedString;
|
||||
import java.util.Hashtable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
class LineBreakPanel extends JPanel implements ActionListener {
|
||||
|
||||
private float textTracking = 0.0f;
|
||||
private static String fontName = "Dialog";
|
||||
private static String text = "This is a long line of text that should be broken across multiple lines. "
|
||||
+ "Please set the different tracking values to test via menu! This test should pass if "
|
||||
+ "these lines are broken to fit the 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.";
|
||||
|
||||
private LineBreakMeasurer lineMeasurer;
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
textTracking = (float)((JRadioButtonMenuItem)e.getSource()).getClientProperty( "tracking" );
|
||||
lineMeasurer = null;
|
||||
invalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
setBackground(Color.white);
|
||||
|
||||
Graphics2D g2d = (Graphics2D)g;
|
||||
|
||||
if (lineMeasurer == null) {
|
||||
Float regular = Float.valueOf(16.0f);
|
||||
Float big = Float.valueOf(24.0f);
|
||||
|
||||
Hashtable map = new Hashtable();
|
||||
map.put(TextAttribute.SIZE, (float)18.0);
|
||||
map.put(TextAttribute.TRACKING, (float)textTracking);
|
||||
|
||||
AttributedString astr = new AttributedString(text, map);
|
||||
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);
|
||||
|
||||
FontRenderContext frc = g2d.getFontRenderContext();
|
||||
lineMeasurer = new LineBreakMeasurer(astr.getIterator(), frc);
|
||||
}
|
||||
|
||||
lineMeasurer.setPosition(0);
|
||||
|
||||
float w = (float)getSize().width;
|
||||
float x = 0, y = 0;
|
||||
TextLayout layout;
|
||||
while ((layout = lineMeasurer.nextLayout(w)) != null) {
|
||||
x = layout.isLeftToRight() ? 0 : w - layout.getAdvance();
|
||||
y += layout.getAscent();
|
||||
layout.draw(g2d, x, y);
|
||||
y += layout.getDescent() + layout.getLeading();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LineBreakWithTracking {
|
||||
|
||||
private static final String INSTRUCTIONS = """
|
||||
This manual test verifies that LineBreakMeasurer measures the lines'
|
||||
breaks correctly taking into account the TextAttribute.TRACKING value.
|
||||
The test string includes Latin, Arabic, CJK and Hebrew.
|
||||
|
||||
You should choose a tracking value from the menu and resize the window.
|
||||
If the text lines break exactly to the wrapping width:
|
||||
no room for one more word exists and
|
||||
the text lines are not too long for given wrapping width, -
|
||||
then press PASS, otherwise - FAIL.
|
||||
""";
|
||||
|
||||
public void createGUI(JFrame frame) {
|
||||
|
||||
LineBreakPanel panel = new LineBreakPanel();
|
||||
frame.getContentPane().add(panel, BorderLayout.CENTER);
|
||||
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
|
||||
JMenu menu = new JMenu("Tracking");
|
||||
ButtonGroup btnGroup = new ButtonGroup();
|
||||
String btnLabels[] = {"-0.1", "0", "0.1", "0.2", "0.3"};
|
||||
float val = -0.1f;
|
||||
for (String label : btnLabels) {
|
||||
JRadioButtonMenuItem btn = new JRadioButtonMenuItem(label);
|
||||
btn.putClientProperty( "tracking", val );
|
||||
btn.addActionListener(panel);
|
||||
btnGroup.add(btn);
|
||||
menu.add(btn);
|
||||
val += 0.1f;
|
||||
}
|
||||
menuBar.add(menu);
|
||||
|
||||
frame.setJMenuBar(menuBar);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException, InvocationTargetException {
|
||||
|
||||
JFrame frame = new JFrame("LineBreakMeasurer with Tracking");
|
||||
frame.setSize(new Dimension(640, 480));
|
||||
|
||||
LineBreakWithTracking controller = new LineBreakWithTracking();
|
||||
controller.createGUI(frame);
|
||||
|
||||
PassFailJFrame passFailJFrame = new PassFailJFrame(INSTRUCTIONS);
|
||||
PassFailJFrame.addTestWindow(frame);
|
||||
PassFailJFrame.positionTestWindow(frame, PassFailJFrame.Position.HORIZONTAL);
|
||||
frame.setVisible(true);
|
||||
passFailJFrame.awaitAndCheck();
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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 8165943
|
||||
@summary LineBreakMeasurer does not measure correctly if TextAttribute.TRACKING is set
|
||||
@run main/othervm LineBreakWithTrackingAuto
|
||||
*/
|
||||
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.LineBreakMeasurer;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.text.AttributedString;
|
||||
|
||||
public class LineBreakWithTrackingAuto {
|
||||
|
||||
private static final String WORD = "word";
|
||||
private static final String SPACE = " ";
|
||||
private static final int NUM_WORDS = 12;
|
||||
private static final float FONT_SIZE = 24.0f;
|
||||
private static final float TEXT_TRACKING[] = { -0.1f, 0f, 0.1f, 0.2f, 0.3f };
|
||||
private static final float EPSILON = 0.005f;
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
new LineBreakWithTrackingAuto().test();
|
||||
}
|
||||
|
||||
public void test() {
|
||||
|
||||
final FontRenderContext frc = new FontRenderContext(null, false, false);
|
||||
|
||||
// construct a paragraph as follows: [SPACE + WORD] + ...
|
||||
StringBuffer text = new StringBuffer();
|
||||
for (int i = 0; i < NUM_WORDS; i++) {
|
||||
text.append(SPACE);
|
||||
text.append(WORD);
|
||||
}
|
||||
AttributedString attrString = new AttributedString(text.toString());
|
||||
attrString.addAttribute(TextAttribute.SIZE, Float.valueOf(FONT_SIZE));
|
||||
|
||||
// test different tracking values: -0.1f, 0f, 0.1f, 0.2f, 0.3f
|
||||
for (float textTracking : TEXT_TRACKING) {
|
||||
|
||||
final float trackingAdvance = FONT_SIZE * textTracking;
|
||||
attrString.addAttribute(TextAttribute.TRACKING, textTracking);
|
||||
|
||||
LineBreakMeasurer measurer = new LineBreakMeasurer(attrString.getIterator(), frc);
|
||||
|
||||
final int sequenceLength = WORD.length() + SPACE.length();
|
||||
final float sequenceAdvance = getSequenceAdvance(measurer, text.length(), sequenceLength);
|
||||
final float textAdvance = NUM_WORDS * sequenceAdvance;
|
||||
|
||||
// test different wrapping width starting from the WORD+SPACE to TEXT width
|
||||
for (float wrappingWidth = sequenceAdvance; wrappingWidth < textAdvance; wrappingWidth += sequenceAdvance / sequenceLength) {
|
||||
|
||||
measurer.setPosition(0);
|
||||
|
||||
// break a paragraph into lines that fit the given wrapping width
|
||||
do {
|
||||
TextLayout layout = measurer.nextLayout(wrappingWidth);
|
||||
float visAdvance = layout.getVisibleAdvance();
|
||||
|
||||
int currPos = measurer.getPosition();
|
||||
if ((trackingAdvance <= 0 && visAdvance - wrappingWidth > EPSILON)
|
||||
|| (trackingAdvance > 0 && visAdvance - wrappingWidth > trackingAdvance + EPSILON)) {
|
||||
throw new Error("text line is too long for given wrapping width");
|
||||
}
|
||||
|
||||
if (currPos < text.length() && visAdvance <= wrappingWidth - sequenceAdvance) {
|
||||
throw new Error("text line is too short for given wrapping width");
|
||||
}
|
||||
} while (measurer.getPosition() != text.length());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float getSequenceAdvance(LineBreakMeasurer measurer, int textLength, int sequenceLength) {
|
||||
|
||||
measurer.setPosition(textLength - sequenceLength);
|
||||
|
||||
TextLayout layout = measurer.nextLayout(10000.0f);
|
||||
if (layout.getCharacterCount() != sequenceLength) {
|
||||
throw new Error("layout length is incorrect");
|
||||
}
|
||||
|
||||
return layout.getVisibleAdvance();
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user