Skip to content

Commit 920a054

Browse files
committed
Fix font selection for whitespace chars
DEVSIX-9212
1 parent 8095630 commit 920a054

File tree

14 files changed

+123
-20
lines changed

14 files changed

+123
-20
lines changed

layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/AbstractFontSelectorStrategy.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,22 @@ public List<Tuple2<GlyphLine, PdfFont>> getGlyphLines(String text) {
103103
if (codePoint > 0xFFFF) {
104104
i++;
105105
}
106-
if (isCurrentFontCheckRequired() && (i != indexDiacritic - 1)
107-
&& !TextUtil.isWhitespaceOrNonPrintable(codePoint)) {
108-
if (currentFont != matchFont(codePoint, fontSelector, fontProvider, additionalFonts)) {
106+
if (isCurrentFontCheckRequired() && (i != indexDiacritic - 1)) {
107+
PdfFont pdfFont = matchFont(codePoint, fontSelector, fontProvider, additionalFonts);
108+
int nextSignificant = nextSignificantIndex(i, text);
109+
// if below describes the case when non-significant (e.g. whitespace, soft-hyphen)
110+
// symbol is surrounded by significant symbols
111+
if (nextSignificant != i && nextSignificant < text.length()) {
112+
PdfFont nextFont = matchFont(extractCodePoint(text, nextSignificant), fontSelector, fontProvider, additionalFonts);
113+
// currentFont - font to the left of current non-significant symbol
114+
// pdfFont - font of the non-significant symbol
115+
// nextFont - font to the right of current non-significant symbol
116+
if (currentFont != pdfFont && nextFont != currentFont) {
117+
// In this case it means that non-significant symbol will be written by nextFont
118+
// (after break currentFont will be selected based on nextSignificantIndex(index, text))
119+
breakRequested = true;
120+
}
121+
} else if (pdfFont != null && currentFont != pdfFont) {
109122
breakRequested = true;
110123
}
111124
}

layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategy.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ protected boolean isCurrentFontCheckRequired() {
5353

5454
/**
5555
* The factory for {@link FirstMatchFontSelectorStrategy}.
56+
*
57+
* @deprecated in favour of {@link FirstMatchFontSelectorStrategyFactory} (typo fix in word "Match")
5658
*/
59+
@Deprecated
5760
public static final class FirstMathFontSelectorStrategyFactory implements IFontSelectorStrategyFactory {
5861
/**
5962
* {@inheritDoc}
@@ -64,4 +67,18 @@ public IFontSelectorStrategy createFontSelectorStrategy(FontProvider fontProvide
6467
return new FirstMatchFontSelectorStrategy(fontProvider, fontSelector, additionalFonts);
6568
}
6669
}
70+
71+
/**
72+
* The factory for {@link FirstMatchFontSelectorStrategy}.
73+
*/
74+
public static final class FirstMatchFontSelectorStrategyFactory implements IFontSelectorStrategyFactory {
75+
/**
76+
* {@inheritDoc}
77+
*/
78+
@Override
79+
public IFontSelectorStrategy createFontSelectorStrategy(FontProvider fontProvider, FontSelector fontSelector,
80+
FontSet additionalFonts) {
81+
return new FirstMatchFontSelectorStrategy(fontProvider, fontSelector, additionalFonts);
82+
}
83+
}
6784
}

layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/BestMatchFontSelectorStrategyTest.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ This file is part of the iText (R) project.
3030

3131
import java.util.List;
3232
import org.junit.jupiter.api.Assertions;
33-
import org.junit.jupiter.api.Test;
3433
import org.junit.jupiter.api.Tag;
34+
import org.junit.jupiter.api.Test;
3535

3636
@Tag("UnitTest")
3737
public class BestMatchFontSelectorStrategyTest extends ExtendedITextTest {
@@ -43,8 +43,8 @@ public void twoDiacriticsInRowTest() {
4343
"L with accent: \u004f\u0301\u0302 abc");
4444
Assertions.assertEquals(3, result.size());
4545
Assertions.assertEquals("L with accent: ", result.get(0).getFirst().toString());
46-
Assertions.assertEquals("\u004f\u0301\u0302 ", result.get(1).getFirst().toString());
47-
Assertions.assertEquals("abc", result.get(2).getFirst().toString());
46+
Assertions.assertEquals("\u004f\u0301\u0302", result.get(1).getFirst().toString());
47+
Assertions.assertEquals(" abc", result.get(2).getFirst().toString());
4848
// Diacritics and symbol were separated, but the font is the same
4949
Assertions.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
5050
}
@@ -57,8 +57,8 @@ public void oneDiacriticTest() {
5757
"L with accent: \u004f\u0302 abc");
5858
Assertions.assertEquals(3, result.size());
5959
Assertions.assertEquals("L with accent: ", result.get(0).getFirst().toString());
60-
Assertions.assertEquals("\u004f\u0302 ", result.get(1).getFirst().toString());
61-
Assertions.assertEquals("abc", result.get(2).getFirst().toString());
60+
Assertions.assertEquals("\u004f\u0302", result.get(1).getFirst().toString());
61+
Assertions.assertEquals(" abc", result.get(2).getFirst().toString());
6262
Assertions.assertNotEquals(result.get(0).getSecond(), result.get(1).getSecond());
6363
}
6464

@@ -112,8 +112,8 @@ public void surrogatePairsTest() {
112112
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
113113
"text \uD800\uDF10\uD800\uDF00\uD800\uDF11 text");
114114
Assertions.assertEquals(3, result.size());
115-
Assertions.assertEquals("text ", result.get(0).getFirst().toString());
116-
Assertions.assertEquals("\uD800\uDF10\uD800\uDF00\uD800\uDF11 ", result.get(1).getFirst().toString());
115+
Assertions.assertEquals("text", result.get(0).getFirst().toString());
116+
Assertions.assertEquals(" \uD800\uDF10\uD800\uDF00\uD800\uDF11 ", result.get(1).getFirst().toString());
117117
Assertions.assertEquals("text", result.get(2).getFirst().toString());
118118
Assertions.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
119119
}
@@ -151,4 +151,52 @@ public void windowsLineEndingsTest() {
151151
Assertions.assertEquals(1, result.size());
152152
Assertions.assertEquals("Hello\r\n World!\r\n ", result.get(0).getFirst().toString());
153153
}
154+
155+
@Test
156+
public void nonSignificantRequiresFontChangeToRightTest() {
157+
IFontSelectorStrategy strategy =
158+
FontSelectorTestsUtil.createStrategyWithNotoSansCJKAndFreeSans(new BestMatchFontSelectorStrategyFactory());
159+
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
160+
// u3000 is ideographicSpace from CJK, the same as u5F53 and u65B9
161+
"EC50:\u3000\u5F53\u65B9");
162+
163+
Assertions.assertEquals(2, result.size());
164+
Assertions.assertEquals("\u3000\u5F53\u65B9", result.get(1).getFirst().toString());
165+
}
166+
167+
@Test
168+
public void nonSignificantRequiresFontChangeToLeftTest() {
169+
IFontSelectorStrategy strategy =
170+
FontSelectorTestsUtil.createStrategyWithNotoSansCJKAndFreeSans(new BestMatchFontSelectorStrategyFactory());
171+
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
172+
// u3000 is ideographicSpace from CJK, the same as u5F53 and u65B9
173+
"\u5F53\u65B9\u3000:EC50");
174+
175+
Assertions.assertEquals(2, result.size());
176+
Assertions.assertEquals(":EC50", result.get(1).getFirst().toString());
177+
}
178+
179+
@Test
180+
public void nonSignificantRequiresFontChangeBetweenSameFontsTest() {
181+
IFontSelectorStrategy strategy =
182+
FontSelectorTestsUtil.createStrategyWithNotoSansCJKAndFreeSans(new BestMatchFontSelectorStrategyFactory());
183+
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
184+
// u3000 is ideographicSpace from CJK
185+
"EC50:\u3000:EC50");
186+
187+
Assertions.assertEquals(1, result.size());
188+
Assertions.assertEquals("EC50:\u3000:EC50", result.get(0).getFirst().toString());
189+
}
190+
191+
@Test
192+
public void nonSignificantRequiresFontChangeToNullFontTest() {
193+
IFontSelectorStrategy strategy =
194+
FontSelectorTestsUtil.createStrategyWithNotoSansCJKAndFreeSans(new BestMatchFontSelectorStrategyFactory());
195+
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
196+
// u3000 is ideographicSpace from CJK, uD800 + uDF10 is surrogate pair with no font
197+
"EC50:\u3000\uD800\uDF10");
198+
199+
Assertions.assertEquals(2, result.size());
200+
Assertions.assertEquals("\u3000\uD800\uDF10", result.get(1).getFirst().toString());
201+
}
154202
}

layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategyTest.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.commons.datastructures.Tuple2;
2626
import com.itextpdf.io.font.otf.GlyphLine;
2727
import com.itextpdf.kernel.font.PdfFont;
28-
import com.itextpdf.layout.font.selectorstrategy.FirstMatchFontSelectorStrategy.FirstMathFontSelectorStrategyFactory;
28+
import com.itextpdf.layout.font.selectorstrategy.FirstMatchFontSelectorStrategy.FirstMatchFontSelectorStrategyFactory;
2929
import com.itextpdf.test.ExtendedITextTest;
3030

3131
import java.util.List;
3232
import org.junit.jupiter.api.Assertions;
33-
import org.junit.jupiter.api.Test;
3433
import org.junit.jupiter.api.Tag;
34+
import org.junit.jupiter.api.Test;
3535

3636
@Tag("UnitTest")
3737
public class FirstMatchFontSelectorStrategyTest extends ExtendedITextTest {
3838
@Test
3939
public void twoDiacriticsInRowTest() {
40-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new FirstMathFontSelectorStrategyFactory());
40+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new FirstMatchFontSelectorStrategyFactory());
4141

4242
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
4343
"L with accent: \u004f\u0301\u0302 abc");
@@ -48,7 +48,7 @@ public void twoDiacriticsInRowTest() {
4848

4949
@Test
5050
public void oneDiacriticTest() {
51-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new FirstMathFontSelectorStrategyFactory());
51+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new FirstMatchFontSelectorStrategyFactory());
5252

5353
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
5454
"L with accent: \u004f\u0302 abc");
@@ -60,7 +60,7 @@ public void oneDiacriticTest() {
6060

6161
@Test
6262
public void diacriticFontDoesnotContainPreviousSymbolTest() {
63-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithNotoSans(new FirstMathFontSelectorStrategyFactory());
63+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithNotoSans(new FirstMatchFontSelectorStrategyFactory());
6464

6565
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
6666
"Ми\u0301ръ (mírə)");
@@ -77,7 +77,7 @@ public void diacriticFontDoesnotContainPreviousSymbolTest() {
7777

7878
@Test
7979
public void oneDiacriticWithUnsupportedFontTest() {
80-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithTNR(new FirstMathFontSelectorStrategyFactory());
80+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithTNR(new FirstMatchFontSelectorStrategyFactory());
8181

8282
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
8383
"L with accent: \u004f\u0302 abc");
@@ -91,7 +91,7 @@ public void oneDiacriticWithUnsupportedFontTest() {
9191

9292
@Test
9393
public void oneDiacriticWithOneSupportedFontTest() {
94-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSans(new FirstMathFontSelectorStrategyFactory());
94+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSans(new FirstMatchFontSelectorStrategyFactory());
9595

9696
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
9797
"L with accent: \u004f\u0302 abc");
@@ -101,7 +101,7 @@ public void oneDiacriticWithOneSupportedFontTest() {
101101

102102
@Test
103103
public void surrogatePairsTest() {
104-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithOldItalic(new FirstMathFontSelectorStrategyFactory());
104+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithOldItalic(new FirstMatchFontSelectorStrategyFactory());
105105

106106
// this text contains three successive surrogate pairs
107107
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
@@ -115,7 +115,7 @@ public void surrogatePairsTest() {
115115

116116
@Test
117117
public void simpleThreeFontTest() {
118-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new FirstMathFontSelectorStrategyFactory());
118+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new FirstMatchFontSelectorStrategyFactory());
119119

120120
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines("abcdefxyz");
121121
Assertions.assertEquals(1, result.size());
@@ -124,10 +124,21 @@ public void simpleThreeFontTest() {
124124

125125
@Test
126126
public void threeFontWithSpacesTest() {
127-
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new FirstMathFontSelectorStrategyFactory());
127+
IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new FirstMatchFontSelectorStrategyFactory());
128128

129129
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(" axadefa ");
130130
Assertions.assertEquals(1, result.size());
131131
Assertions.assertEquals(" axadefa ", result.get(0).getFirst().toString());
132132
}
133+
134+
@Test
135+
public void ideographicSpaceRequireFontChangeTest() {
136+
IFontSelectorStrategy strategy =
137+
FontSelectorTestsUtil.createStrategyWithNotoSansCJKAndFreeSans(new FirstMatchFontSelectorStrategyFactory());
138+
final List<Tuple2<GlyphLine, PdfFont>> result = strategy.getGlyphLines(
139+
"EC50:\u3000\u5F53\u65B9");
140+
141+
Assertions.assertEquals(2, result.size());
142+
Assertions.assertEquals("\u5F53\u65B9", result.get(1).getFirst().toString());
143+
}
133144
}

layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FontSelectorTestsUtil.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,18 @@ public static IFontSelectorStrategy createStrategyWithOldItalic(IFontSelectorStr
105105
fontFamilies.add("random");
106106
return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
107107
}
108+
109+
public static IFontSelectorStrategy createStrategyWithNotoSansCJKAndFreeSans(IFontSelectorStrategyFactory factory) {
110+
FontSet fs = new FontSet();
111+
fs.addFont(FONTS_FOLDER + "FreeSans.ttf");
112+
fs.addFont(FONTS_FOLDER + "NotoSansCJKjp-Regular.otf");
113+
114+
final FontProvider fontProvider = new FontProvider(fs, StandardFontFamilies.TIMES);
115+
fontProvider.setFontSelectorStrategyFactory(factory);
116+
117+
List<String> fontFamilies = new ArrayList<>();
118+
fontFamilies.add("random");
119+
120+
return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
121+
}
108122
}
Binary file not shown.

0 commit comments

Comments
 (0)