Skip to content

Commit d662066

Browse files
Introduce tests for FontCache, fix ByteCid AbstractCmap implementation
Also minor refactoring was performed and documentation was improvemed. DEVSIX-3297
1 parent 2fb422d commit d662066

File tree

5 files changed

+284
-93
lines changed

5 files changed

+284
-93
lines changed

io/src/main/java/com/itextpdf/io/font/FontCache.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ This file is part of the iText (R) project.
5757
import java.io.InputStream;
5858
import java.util.HashMap;
5959
import java.util.HashSet;
60+
import java.util.LinkedHashMap;
6061
import java.util.Map;
6162
import java.util.Properties;
6263
import java.util.Set;
@@ -65,7 +66,7 @@ This file is part of the iText (R) project.
6566

6667
public class FontCache {
6768

68-
private static final Map<String, Map<String, Object>> allCidFonts = new HashMap<>();
69+
private static final Map<String, Map<String, Object>> allCidFonts = new LinkedHashMap<>();
6970
private static final Map<String, Set<String>> registryNames = new HashMap<>();
7071

7172
private static final String CJK_REGISTRY_FILENAME = "cjk_registry.properties";
@@ -101,6 +102,11 @@ protected static boolean isPredefinedCidFont(String fontName) {
101102
return true;
102103
}
103104

105+
/**
106+
* Finds a CJK font family which is compatible to the given CMap.
107+
* @param cmap a name of the CMap for which compatible font is searched.
108+
* @return a CJK font name if there's known compatible font for the given cmap name, or null otherwise.
109+
*/
104110
public static String getCompatibleCidFont(String cmap) {
105111
for (Map.Entry<String, Set<String>> e : registryNames.entrySet()) {
106112
if (e.getValue().contains(cmap)) {
@@ -114,8 +120,18 @@ public static String getCompatibleCidFont(String cmap) {
114120
return null;
115121
}
116122

123+
/**
124+
* Finds all CMap names that belong to the same registry to which a given
125+
* font belongs.
126+
* @param fontName a name of the font for which CMap's are searched.
127+
* @return a set of CMap names corresponding to the given font.
128+
*/
117129
public static Set<String> getCompatibleCmaps(String fontName) {
118-
String registry = (String) FontCache.getAllPredefinedCidFonts().get(fontName).get(REGISTRY_PROP);
130+
Map<String, Object> cidFonts = FontCache.getAllPredefinedCidFonts().get(fontName);
131+
if (cidFonts == null) {
132+
return null;
133+
}
134+
String registry = (String) cidFonts.get(REGISTRY_PROP);
119135
return registryNames.get(registry);
120136
}
121137

@@ -127,6 +143,11 @@ public static Map<String, Set<String>> getRegistryNames() {
127143
return registryNames;
128144
}
129145

146+
/**
147+
* Parses CMap with a given name producing it in a form of cid to unicode mapping.
148+
* @param uniMap a CMap name. It is expected that CMap identified by this name defines unicode to cid mapping.
149+
* @return an object for convenient mapping from cid to unicode. If no CMap was found for provided name an exception is thrown.
150+
*/
130151
public static CMapCidUni getCid2UniCmap(String uniMap) {
131152
CMapCidUni cidUni = new CMapCidUni();
132153
return parseCmap(uniMap, cidUni);

io/src/main/java/com/itextpdf/io/font/cmap/CMapByteCid.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,16 @@ public Cursor(int offset, int length) {
6666
}
6767
}
6868

69-
private List<char[]> planes = new ArrayList<>();
69+
private List<int[]> planes = new ArrayList<>();
7070

7171
public CMapByteCid() {
72-
planes.add(new char[256]);
72+
planes.add(new int[256]);
7373
}
7474

7575
@Override
7676
void addChar(String mark, CMapObject code) {
7777
if (code.isNumber()) {
78-
encodeSequence(decodeStringToByte(mark), (char)code.getValue());
78+
encodeSequence(decodeStringToByte(mark), (int) code.getValue());
7979
}
8080
}
8181

@@ -101,7 +101,7 @@ protected int decodeSingle(byte[] cidBytes, Cursor cursor) {
101101
while (cursor.offset < end) {
102102
int one = cidBytes[cursor.offset++] & 0xff;
103103
cursor.length--;
104-
char[] plane = planes.get(currentPlane);
104+
int[] plane = planes.get(currentPlane);
105105
int cid = plane[one];
106106
if ((cid & 0x8000) == 0) {
107107
return cid;
@@ -112,25 +112,25 @@ protected int decodeSingle(byte[] cidBytes, Cursor cursor) {
112112
return -1;
113113
}
114114

115-
private void encodeSequence(byte[] seq, char cid) {
115+
private void encodeSequence(byte[] seq, int cid) {
116116
int size = seq.length - 1;
117117
int nextPlane = 0;
118118
for (int idx = 0; idx < size; ++idx) {
119-
char[] plane = planes.get(nextPlane);
119+
int[] plane = planes.get(nextPlane);
120120
int one = seq[idx] & 0xff;
121-
char c = plane[one];
121+
int c = plane[one];
122122
if (c != 0 && (c & 0x8000) == 0)
123123
throw new IOException("Inconsistent mapping.");
124124
if (c == 0) {
125-
planes.add(new char[256]);
126-
c = (char)(planes.size() - 1 | 0x8000);
125+
planes.add(new int[256]);
126+
c = (planes.size() - 1 | 0x8000);
127127
plane[one] = c;
128128
}
129129
nextPlane = c & 0x7fff;
130130
}
131-
char[] plane = planes.get(nextPlane);
131+
int[] plane = planes.get(nextPlane);
132132
int one = seq[size] & 0xff;
133-
char c = plane[one];
133+
int c = plane[one];
134134
if ((c & 0x8000) != 0)
135135
throw new IOException("Inconsistent mapping.");
136136
plane[one] = cid;
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2019 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License version 3
8+
as published by the Free Software Foundation with the addition of the
9+
following permission added to Section 15 as permitted in Section 7(a):
10+
FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
11+
ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
12+
OF THIRD PARTY RIGHTS
13+
14+
This program is distributed in the hope that it will be useful, but
15+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16+
or FITNESS FOR A PARTICULAR PURPOSE.
17+
See the GNU Affero General Public License for more details.
18+
You should have received a copy of the GNU Affero General Public License
19+
along with this program; if not, see http://www.gnu.org/licenses or write to
20+
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21+
Boston, MA, 02110-1301 USA, or download the license from the following URL:
22+
http://itextpdf.com/terms-of-use/
23+
24+
The interactive user interfaces in modified source and object code versions
25+
of this program must display Appropriate Legal Notices, as required under
26+
Section 5 of the GNU Affero General Public License.
27+
28+
In accordance with Section 7(b) of the GNU Affero General Public License,
29+
a covered work must retain the producer line in every PDF that is created
30+
or manipulated using iText.
31+
32+
You can be released from the requirements of the license by purchasing
33+
a commercial license. Buying such a license is mandatory as soon as you
34+
develop commercial activities involving the iText software without
35+
disclosing the source code of your own applications.
36+
These activities include: offering paid services to customers as an ASP,
37+
serving PDFs on the fly in a web application, shipping iText with a closed
38+
source product.
39+
40+
For more information, please contact iText Software Corp. at this
41+
42+
*/
43+
package com.itextpdf.io.font;
44+
45+
import com.itextpdf.io.IOException;
46+
import com.itextpdf.io.font.cmap.CMapCidUni;
47+
import com.itextpdf.io.font.otf.Glyph;
48+
import com.itextpdf.io.util.MessageFormatUtil;
49+
import com.itextpdf.test.ExtendedITextTest;
50+
import com.itextpdf.test.annotations.type.UnitTest;
51+
import java.nio.charset.StandardCharsets;
52+
import org.junit.Assert;
53+
import org.junit.Before;
54+
import org.junit.Rule;
55+
import org.junit.Test;
56+
import org.junit.experimental.categories.Category;
57+
import org.junit.rules.ExpectedException;
58+
59+
@Category(UnitTest.class)
60+
public class FontCacheNoFontAsianTest extends ExtendedITextTest {
61+
62+
@Rule
63+
public ExpectedException junitExpectedException = ExpectedException.none();
64+
65+
@Before
66+
public void before() {
67+
FontCache.clearSavedFonts();
68+
}
69+
70+
@Test
71+
public void clearFontCacheTest() {
72+
String fontName = "FreeSans.ttf";
73+
Assert.assertNull(FontCache.getFont(fontName));
74+
75+
FontProgram fontProgram = new FontProgramMock();
76+
FontCache.saveFont(fontProgram, fontName);
77+
Assert.assertEquals(fontProgram, FontCache.getFont(fontName));
78+
79+
FontCache.clearSavedFonts();
80+
Assert.assertNull(FontCache.getFont(fontName));
81+
}
82+
83+
@Test
84+
public void fontStringTtcCacheKeyTest() {
85+
String fontName = "Font.ttc";
86+
87+
FontCacheKey ttc0 = FontCacheKey.create(fontName, 0);
88+
FontCacheKey ttc1 = FontCacheKey.create(fontName, 1);
89+
90+
Assert.assertNull(FontCache.getFont(ttc0));
91+
Assert.assertNull(FontCache.getFont(ttc1));
92+
93+
FontProgram fontProgram = new FontProgramMock();
94+
FontCache.saveFont(fontProgram, ttc1);
95+
96+
Assert.assertNull(FontCache.getFont(ttc0));
97+
Assert.assertEquals(fontProgram, FontCache.getFont(ttc1));
98+
}
99+
100+
@Test
101+
public void fontBytesTtcCacheKeyTest() {
102+
byte[] fontBytes = "SupposedTtcFontData".getBytes(StandardCharsets.UTF_8);
103+
byte[] otherFontBytes = "DifferentTtcFontBytes".getBytes(StandardCharsets.UTF_8);
104+
byte[] normalFontBytes = "NormalFontBytes".getBytes(StandardCharsets.UTF_8);
105+
106+
FontCacheKey ttc0 = FontCacheKey.create(fontBytes, 1);
107+
FontCacheKey otherTtc0 = FontCacheKey.create(otherFontBytes, 1);
108+
FontCacheKey normal = FontCacheKey.create(normalFontBytes);
109+
110+
Assert.assertNull(FontCache.getFont(ttc0));
111+
Assert.assertNull(FontCache.getFont(otherTtc0));
112+
Assert.assertNull(FontCache.getFont(normal));
113+
114+
FontProgram otherTtc0MockFontProgram = new FontProgramMock();
115+
FontProgram normalMockFontProgram = new FontProgramMock();
116+
FontCache.saveFont(otherTtc0MockFontProgram, otherTtc0);
117+
FontCache.saveFont(normalMockFontProgram, normal);
118+
119+
Assert.assertNull(FontCache.getFont(ttc0));
120+
Assert.assertEquals(otherTtc0MockFontProgram, FontCache.getFont(otherTtc0));
121+
Assert.assertEquals(normalMockFontProgram, FontCache.getFont(normal));
122+
}
123+
124+
@Test
125+
public void getCompatibleCidFontNoFontAsian() {
126+
// Without font-asian module in the class path
127+
// any value passed into a method is expected to return null.
128+
Assert.assertNull(FontCache.getCompatibleCidFont("78-RKSJ-V"));
129+
}
130+
131+
@Test
132+
public void isPredefinedCidFontNoFontAsian() {
133+
// Without font-asian module in the class path
134+
// any value passed into a method is expected to return false.
135+
Assert.assertFalse(FontCache.isPredefinedCidFont("78-RKSJ-V"));
136+
}
137+
138+
@Test
139+
public void getCompatibleCmapsNoFontAsian() {
140+
// Without font-asian module in the class path
141+
// any value passed into a method is expected to return null.
142+
Assert.assertNull(FontCache.getCompatibleCmaps("HeiseiKakuGo-W5"));
143+
}
144+
145+
@Test
146+
public void getRegistryNamesNoFontAsian() {
147+
// Without font-asian module in the class path
148+
// registry names collection is expected to be empty.
149+
Assert.assertTrue(FontCache.getRegistryNames().isEmpty());
150+
}
151+
152+
@Test
153+
public void getCid2UniCMapNoFontAsian() {
154+
junitExpectedException.expect(IOException.class);
155+
156+
// Without font-asian module in the class path
157+
// no CMap can be found.
158+
FontCache.getCid2UniCmap("UniJIS-UTF16-H");
159+
}
160+
161+
@Test
162+
public void getUni2CidCMapNoFontAsian() {
163+
junitExpectedException.expect(IOException.class);
164+
165+
// Without font-asian module in the class path
166+
// no CMap can be found.
167+
FontCache.getUni2CidCmap("UniJIS-UTF16-H");
168+
}
169+
170+
@Test
171+
public void getByte2CidCMapNoFontAsian() {
172+
junitExpectedException.expect(IOException.class);
173+
174+
// Without font-asian module in the class path
175+
// no CMap can be found.
176+
FontCache.getByte2CidCmap("78ms-RKSJ-H");
177+
}
178+
179+
@Test
180+
public void getCid2ByteCMapNoFontAsian() {
181+
junitExpectedException.expect(IOException.class);
182+
183+
// Without font-asian module in the class path
184+
// no CMap can be found.
185+
FontCache.getCid2Byte("78ms-RKSJ-H");
186+
}
187+
188+
private static class FontProgramMock extends FontProgram {
189+
190+
@Override
191+
public int getPdfFontFlags() {
192+
return 0;
193+
}
194+
195+
@Override
196+
public int getKerning(Glyph first, Glyph second) {
197+
return 0;
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)