Skip to content

Commit 8edb98d

Browse files
Olga Mikhaltsovaazul-jf
authored andcommitted
8165943: LineBreakMeasurer does not measure correctly if TextAttribute.TRACKING is set.
Co-authored-by: Jason Fordham <jclf@azul.com> Reviewed-by: prr
1 parent 3934484 commit 8edb98d

File tree

5 files changed

+322
-10
lines changed

5 files changed

+322
-10
lines changed

src/java.desktop/share/classes/sun/font/AttributeValues.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,23 @@ public static AffineTransform getCharTransform(Map<?, ?> map) {
846846
return null;
847847
}
848848

849+
@SuppressWarnings("unchecked")
850+
public static float getTracking(Map<?, ?> map) {
851+
if (map != null) {
852+
AttributeValues av = null;
853+
if (map instanceof AttributeMap &&
854+
((AttributeMap) map).getValues() != null) {
855+
av = ((AttributeMap)map).getValues();
856+
} else if (map.get(TextAttribute.TRACKING) != null) {
857+
av = AttributeValues.fromMap((Map<Attribute, ?>)map);
858+
}
859+
if (av != null) {
860+
return av.tracking;
861+
}
862+
}
863+
return 0;
864+
}
865+
849866
public void updateDerivedTransforms() {
850867
// this also updates the mask for the baseline transform
851868
if (transform == null) {

src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
7171
StandardGlyphVector gv;
7272
float[] charinfo;
7373

74+
float advTracking;
75+
7476
/**
7577
* Create from a TextSource.
7678
*/
@@ -110,6 +112,8 @@ private void finishInit() {
110112
source.getStart() + source.getLength(), source.getFRC());
111113
cm = CoreMetrics.get(lm);
112114
}
115+
116+
advTracking = font.getSize() * AttributeValues.getTracking(atts);
113117
}
114118

115119

@@ -378,10 +382,10 @@ public float getCharAdvance(int index) {
378382
validate(index);
379383
float[] charinfo = getCharinfo();
380384
int idx = l2v(index) * numvals + advx;
381-
if (charinfo == null || idx >= charinfo.length) {
385+
if (charinfo == null || idx >= charinfo.length || charinfo[idx] == 0) {
382386
return 0f;
383387
} else {
384-
return charinfo[idx];
388+
return charinfo[idx] + advTracking;
385389
}
386390
}
387391

@@ -477,16 +481,25 @@ public int visualToLogical(int visualIndex) {
477481
}
478482

479483
public int getLineBreakIndex(int start, float width) {
484+
final float epsilon = 0.005f;
485+
480486
float[] charinfo = getCharinfo();
481487
int length = source.getLength();
488+
489+
if (advTracking > 0) {
490+
width += advTracking;
491+
}
492+
482493
--start;
483-
while (width >= 0 && ++start < length) {
494+
while (width >= -epsilon && ++start < length) {
484495
int cidx = l2v(start) * numvals + advx;
485496
if (cidx >= charinfo.length) {
486497
break; // layout bailed for some reason
487498
}
488499
float adv = charinfo[cidx];
489-
width -= adv;
500+
if (adv != 0) {
501+
width -= adv + advTracking;
502+
}
490503
}
491504

492505
return start;
@@ -502,7 +515,10 @@ public float getAdvanceBetween(int start, int limit) {
502515
if (cidx >= charinfo.length) {
503516
break; // layout bailed for some reason
504517
}
505-
a += charinfo[cidx];
518+
float adv = charinfo[cidx];
519+
if (adv != 0) {
520+
a += adv + advTracking;
521+
}
506522
}
507523

508524
return a;

src/java.desktop/share/classes/sun/font/StandardGlyphVector.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,21 +197,22 @@ public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float
197197

198198
// how do we know its a base glyph
199199
// for now, it is if the natural advance of the glyph is non-zero
200-
Font2D f2d = FontUtilities.getFont2D(font);
201-
FontStrike strike = f2d.getStrike(font, frc);
202200

203201
float[] deltas = { trackPt.x, trackPt.y };
204202
for (int j = 0; j < deltas.length; ++j) {
205203
float inc = deltas[j];
204+
float prevPos = 0;
206205
if (inc != 0) {
207206
float delta = 0;
208-
for (int i = j, n = 0; n < glyphs.length; i += 2) {
209-
if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test
207+
for (int i = j; i < positions.length; i += 2) {
208+
if (i == j || prevPos != positions[i]) {
209+
prevPos = positions[i];
210210
positions[i] += delta;
211211
delta += inc;
212+
} else if (prevPos == positions[i]) {
213+
positions[i] = positions[i - 2];
212214
}
213215
}
214-
positions[positions.length-2+j] += delta;
215216
}
216217
}
217218
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8165943
27+
* @summary LineBreakMeasurer does not measure correctly if TextAttribute.TRACKING is set
28+
* @library ../../regtesthelpers
29+
* @build PassFailJFrame
30+
* @run main/manual LineBreakWithTracking
31+
*/
32+
33+
import javax.swing.*;
34+
import java.awt.*;
35+
import java.awt.event.ActionEvent;
36+
import java.awt.event.ActionListener;
37+
import java.awt.event.WindowAdapter;
38+
import java.awt.event.WindowEvent;
39+
import java.awt.font.FontRenderContext;
40+
import java.awt.font.LineBreakMeasurer;
41+
import java.awt.font.TextAttribute;
42+
import java.awt.font.TextLayout;
43+
import java.text.AttributedString;
44+
import java.util.Hashtable;
45+
import java.lang.reflect.InvocationTargetException;
46+
47+
class LineBreakPanel extends JPanel implements ActionListener {
48+
49+
private float textTracking = 0.0f;
50+
private static String fontName = "Dialog";
51+
private static String text = "This is a long line of text that should be broken across multiple lines. "
52+
+ "Please set the different tracking values to test via menu! This test should pass if "
53+
+ "these lines are broken to fit the width, and fail otherwise. It should "
54+
+ "also format the hebrew (\u05d0\u05d1\u05d2 \u05d3\u05d4\u05d5) and arabic "
55+
+ "(\u0627\u0628\u062a\u062c \u062e\u0644\u0627\u062e) and CJK "
56+
+ "(\u4e00\u4e01\u4e02\uac00\uac01\uc4fa\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7"
57+
+ "\u67b8\u67b9) text correctly.";
58+
59+
private LineBreakMeasurer lineMeasurer;
60+
61+
public void actionPerformed(ActionEvent e) {
62+
textTracking = (float)((JRadioButtonMenuItem)e.getSource()).getClientProperty( "tracking" );
63+
lineMeasurer = null;
64+
invalidate();
65+
repaint();
66+
}
67+
68+
public void paintComponent(Graphics g) {
69+
super.paintComponent(g);
70+
setBackground(Color.white);
71+
72+
Graphics2D g2d = (Graphics2D)g;
73+
74+
if (lineMeasurer == null) {
75+
Float regular = Float.valueOf(16.0f);
76+
Float big = Float.valueOf(24.0f);
77+
78+
Hashtable map = new Hashtable();
79+
map.put(TextAttribute.SIZE, (float)18.0);
80+
map.put(TextAttribute.TRACKING, (float)textTracking);
81+
82+
AttributedString astr = new AttributedString(text, map);
83+
astr.addAttribute(TextAttribute.SIZE, regular, 0, text.length());
84+
astr.addAttribute(TextAttribute.FAMILY, fontName, 0, text.length());
85+
86+
int ix = text.indexOf("broken");
87+
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
88+
ix = text.indexOf("hebrew");
89+
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
90+
ix = text.indexOf("arabic");
91+
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
92+
ix = text.indexOf("CJK");
93+
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 3);
94+
95+
FontRenderContext frc = g2d.getFontRenderContext();
96+
lineMeasurer = new LineBreakMeasurer(astr.getIterator(), frc);
97+
}
98+
99+
lineMeasurer.setPosition(0);
100+
101+
float w = (float)getSize().width;
102+
float x = 0, y = 0;
103+
TextLayout layout;
104+
while ((layout = lineMeasurer.nextLayout(w)) != null) {
105+
x = layout.isLeftToRight() ? 0 : w - layout.getAdvance();
106+
y += layout.getAscent();
107+
layout.draw(g2d, x, y);
108+
y += layout.getDescent() + layout.getLeading();
109+
}
110+
}
111+
}
112+
113+
public class LineBreakWithTracking {
114+
115+
private static final String INSTRUCTIONS = """
116+
This manual test verifies that LineBreakMeasurer measures the lines'
117+
breaks correctly taking into account the TextAttribute.TRACKING value.
118+
The test string includes Latin, Arabic, CJK and Hebrew.
119+
120+
You should choose a tracking value from the menu and resize the window.
121+
If the text lines break exactly to the wrapping width:
122+
no room for one more word exists and
123+
the text lines are not too long for given wrapping width, -
124+
then press PASS, otherwise - FAIL.
125+
""";
126+
127+
public void createGUI(JFrame frame) {
128+
129+
LineBreakPanel panel = new LineBreakPanel();
130+
frame.getContentPane().add(panel, BorderLayout.CENTER);
131+
132+
JMenuBar menuBar = new JMenuBar();
133+
134+
JMenu menu = new JMenu("Tracking");
135+
ButtonGroup btnGroup = new ButtonGroup();
136+
String btnLabels[] = {"-0.1", "0", "0.1", "0.2", "0.3"};
137+
float val = -0.1f;
138+
for (String label : btnLabels) {
139+
JRadioButtonMenuItem btn = new JRadioButtonMenuItem(label);
140+
btn.putClientProperty( "tracking", val );
141+
btn.addActionListener(panel);
142+
btnGroup.add(btn);
143+
menu.add(btn);
144+
val += 0.1f;
145+
}
146+
menuBar.add(menu);
147+
148+
frame.setJMenuBar(menuBar);
149+
}
150+
151+
public static void main(String[] args) throws InterruptedException, InvocationTargetException {
152+
153+
JFrame frame = new JFrame("LineBreakMeasurer with Tracking");
154+
frame.setSize(new Dimension(640, 480));
155+
156+
LineBreakWithTracking controller = new LineBreakWithTracking();
157+
controller.createGUI(frame);
158+
159+
PassFailJFrame passFailJFrame = new PassFailJFrame(INSTRUCTIONS);
160+
PassFailJFrame.addTestWindow(frame);
161+
PassFailJFrame.positionTestWindow(frame, PassFailJFrame.Position.HORIZONTAL);
162+
frame.setVisible(true);
163+
passFailJFrame.awaitAndCheck();
164+
}
165+
}

0 commit comments

Comments
 (0)