Skip to content

Commit 3a592a0

Browse files
committed
fix and test line estimation
1 parent 307d1c2 commit 3a592a0

2 files changed

Lines changed: 175 additions & 1 deletion

File tree

src/main/java/org/htmlunit/platform/font/AwtFontUtil.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ public class AwtFontUtil implements FontUtil {
3131

3232
@Override
3333
public int countLines(final String content, final int pixelWidth, final String fontSize) {
34-
final String[] lines = StringUtils.split(content, '\n');
3534
int lineCount = 0;
35+
if (content == null) {
36+
return lineCount;
37+
}
38+
39+
final String[] lines = StringUtils.splitByWholeSeparatorPreserveAllTokens(content, "\n");
3640
final int fontSizeInt = CssPixelValueConverter.pixelValue(fontSize);
3741
final FontRenderContext fontRenderCtx = new FontRenderContext(null, false, true);
3842
for (final String line : lines) {
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright (c) 2002-2026 Gargoyle Software Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.htmlunit.platform.font;
16+
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
19+
20+
import org.junit.jupiter.api.Test;
21+
22+
/**
23+
* Unit tests for {@link AwtFontUtil}.
24+
*
25+
* @author Ronald Brill
26+
*/
27+
public class AwtFontUtilTest {
28+
29+
private final FontUtil fontUtil_ = new AwtFontUtil();
30+
31+
/**
32+
* A single short line, wide enough box, should never wrap: exactly one line.
33+
*/
34+
@Test
35+
public void countLines_singleShortLine() {
36+
assertEquals(1, fontUtil_.countLines("Hello", 1000, "12px"));
37+
}
38+
39+
/**
40+
* Empty content should report zero lines.
41+
*/
42+
@Test
43+
public void countLines_emptyContent() {
44+
assertEquals(0, fontUtil_.countLines("", 1000, "12px"));
45+
}
46+
47+
/**
48+
* {@code null} content should not throw and should report zero lines,
49+
* relying on the null-safety of {@code StringUtils.split(null, ...)}.
50+
*/
51+
@Test
52+
public void countLines_nullContent() {
53+
assertEquals(0, fontUtil_.countLines(null, 1000, "12px"));
54+
}
55+
56+
/**
57+
* Explicit newlines with real text on each line: two lines in, two lines out
58+
* (given a pixel width wide enough that neither line wraps).
59+
*/
60+
@Test
61+
public void countLines_multipleExplicitLines() {
62+
assertEquals(2, fontUtil_.countLines("Hello\nWorld", 1000, "12px"));
63+
}
64+
65+
/**
66+
* Blank lines (whitespace-only or empty, between explicit newlines) should
67+
* each count as exactly one line rather than being skipped or merged.
68+
* This directly probes the interaction between
69+
* {@code StringUtils.split(content, '\n')} and the blank-line branch.
70+
*/
71+
@Test
72+
public void countLines_blankLineBetweenTextLines() {
73+
assertEquals(3, fontUtil_.countLines("Hello\n\nWorld", 1000, "12px"));
74+
}
75+
76+
/**
77+
* A line consisting only of whitespace should still count as one line.
78+
*/
79+
@Test
80+
public void countLines_whitespaceOnlyLine() {
81+
assertEquals(1, fontUtil_.countLines(" ", 1000, "12px"));
82+
}
83+
84+
/**
85+
* Multiple consecutive blank lines should each be counted individually,
86+
* not collapsed into fewer lines by the underlying split.
87+
*/
88+
@Test
89+
public void countLines_multipleConsecutiveBlankLines() {
90+
assertEquals(5, fontUtil_.countLines("A\n\n\n\nB", 1000, "12px"));
91+
}
92+
93+
/**
94+
* A trailing newline at the end of the content: verifies whether a
95+
* trailing empty segment is (or is not) counted as an extra line.
96+
* Documents current behavior rather than asserting a "correct" answer,
97+
* since the semantics of a trailing newline are easy to get wrong silently.
98+
*/
99+
@Test
100+
public void countLines_trailingNewline() {
101+
final int lines = fontUtil_.countLines("Hello\n", 1000, "12px");
102+
assertTrue(lines == 1 || lines == 2,
103+
"Unexpected line count for trailing newline: " + lines);
104+
}
105+
106+
/**
107+
* A long line with a narrow pixel width should wrap into more than one
108+
* layout line, exercising the {@code LineBreakMeasurer} branch.
109+
*/
110+
@Test
111+
public void countLines_longLineWrapsWithNarrowWidth() {
112+
final String longLine =
113+
"The quick brown fox jumps over the lazy dog. ".repeat(10);
114+
final int wideLines = fontUtil_.countLines(longLine, 5000, "12px");
115+
final int narrowLines = fontUtil_.countLines(longLine, 50, "12px");
116+
117+
assertTrue(narrowLines > wideLines,
118+
"Narrower pixel width should force more line breaks: "
119+
+ "wide=" + wideLines + " narrow=" + narrowLines);
120+
}
121+
122+
/**
123+
* A single line with no whitespace at all (so {@code LineBreakMeasurer}
124+
* cannot find a break point) combined with a very narrow width should not
125+
* loop forever; it must terminate at or before the internal safety cap.
126+
* Currently that cap is a hardcoded {@code 1000} inside
127+
* {@link AwtFontUtil#countLines}.
128+
*/
129+
@Test
130+
public void countLines_unbreakableLineRespectsSafetyCap() {
131+
final String unbreakable = "a".repeat(5000);
132+
final int lines = fontUtil_.countLines(unbreakable, 1, "12px");
133+
134+
assertTrue(lines <= 1000,
135+
"countLines should never exceed the internal safety cap, got: " + lines);
136+
}
137+
138+
/**
139+
* Sanity check that a larger font size, at a fixed pixel width, tends to
140+
* produce at least as many wrapped lines as a smaller font size (larger
141+
* glyphs need more line breaks to fit the same width). This exercises the
142+
* {@code fontSizeInt / 1.1} scaling factor indirectly.
143+
*/
144+
@Test
145+
public void countLines_largerFontSizeWrapsAtLeastAsMuch() {
146+
final String longLine =
147+
"The quick brown fox jumps over the lazy dog. ".repeat(10);
148+
final int smallFontLines = fontUtil_.countLines(longLine, 300, "10px");
149+
final int largeFontLines = fontUtil_.countLines(longLine, 300, "30px");
150+
151+
assertTrue(largeFontLines >= smallFontLines,
152+
"Larger font size should not produce fewer wrapped lines: "
153+
+ "small=" + smallFontLines + " large=" + largeFontLines);
154+
}
155+
156+
/**
157+
* Multiple paragraphs, each independently subject to wrapping, should sum
158+
* their individual line counts.
159+
*/
160+
@Test
161+
public void countLines_multipleWrappingParagraphs() {
162+
final String paragraph = "The quick brown fox jumps over the lazy dog. ".repeat(5);
163+
final String content = paragraph + "\n" + paragraph;
164+
165+
final int singleParagraphLines = fontUtil_.countLines(paragraph, 200, "12px");
166+
final int combinedLines = fontUtil_.countLines(content, 200, "12px");
167+
168+
assertEquals(singleParagraphLines * 2, combinedLines);
169+
}
170+
}

0 commit comments

Comments
 (0)