Skip to content

Commit 7757af2

Browse files
committed
Support CMap streams as /Encoding of Type0 fonts. Support code space ranges of variable lengths
DEVSIX-1058
1 parent 7f5b241 commit 7757af2

File tree

10 files changed

+224
-47
lines changed

10 files changed

+224
-47
lines changed

io/src/main/java/com/itextpdf/io/LogMessageConstant.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public final class LogMessageConstant {
8383
public static final String EXCEPTION_WHILE_CREATING_DEFAULT_FONT = "Exception while creating default font (Helvetica, WinAnsi)";
8484
public static final String EXCEPTION_WHILE_UPDATING_XMPMETADATA = "Exception while updating XmpMetadata";
8585
public static final String EXISTING_TAG_STRUCTURE_ROOT_IS_NOT_STANDARD = "Existing tag structure of the document has a root of \"{0}\" role in \"{1}\" namespace that is not mapped to the standard role.";
86+
public static final String FAILED_TO_DETERMINE_CID_FONT_SUBTYPE = "Failed to determine CIDFont subtype. The type of CIDFont shall be CIDFontType0 or CIDFontType2.";
87+
public static final String FAILED_TO_PARSE_ENCODING_STREAM = "Failed to parse encoding stream.";
8688
public static final String FILE_CHANNEL_CLOSING_FAILED = "Closing of the file channel this source is based on failed.";
8789
public static final String FLUSHED_OBJECT_CONTAINS_FREE_REFERENCE = "Flushed object contains indirect reference which is free. Null object will be written instead.";
8890
public static final String FLUSHED_OBJECT_CONTAINS_REFERENCE_WHICH_NOT_REFER_TO_ANY_OBJECT = "Flushed object contains indirect reference which doesn't refer to any other object. Null object will be written instead.";

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

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,24 @@ This file is part of the iText (R) project.
4343
*/
4444
package com.itextpdf.io.font;
4545

46-
import com.itextpdf.io.util.IntHashtable;
46+
import com.itextpdf.io.LogMessageConstant;
4747
import com.itextpdf.io.font.cmap.CMapCidByte;
4848
import com.itextpdf.io.font.cmap.CMapCidUni;
49+
import com.itextpdf.io.font.cmap.CMapLocationFromBytes;
50+
import com.itextpdf.io.font.cmap.CMapParser;
51+
import com.itextpdf.io.source.ByteBuffer;
52+
import com.itextpdf.io.util.IntHashtable;
53+
import org.slf4j.LoggerFactory;
4954

55+
import java.io.IOException;
5056
import java.io.Serializable;
57+
import java.util.Arrays;
58+
import java.util.List;
5159

5260
public class CMapEncoding implements Serializable {
5361

62+
private static final List<byte[]> IDENTITY_H_V_CODESPACE_RANGES = Arrays.asList(new byte[] {0, 0}, new byte[] {(byte)0xff, (byte)0xff});
63+
5464
private static final long serialVersionUID = 2418291066110642993L;
5565
private String cmap;
5666
private String uniMap;
@@ -63,6 +73,8 @@ public class CMapEncoding implements Serializable {
6373

6474
private IntHashtable code2Cid;
6575

76+
private List<byte[]> codeSpaceRanges;
77+
6678
/**
6779
*
6880
* @param cmap CMap name.
@@ -72,6 +84,9 @@ public CMapEncoding(String cmap) {
7284
if (cmap.equals(PdfEncodings.IDENTITY_H) || cmap.equals(PdfEncodings.IDENTITY_V)) {
7385
isDirect = true;
7486
}
87+
// Actually this constructor is only called for Identity-H/V cmaps currently.
88+
// Even for hypothetical case of non-Identity-H/V, let's use Identity-H/V ranges (two byte ranges) for compatibility with previous behavior
89+
this.codeSpaceRanges = IDENTITY_H_V_CODESPACE_RANGES;
7590
}
7691

7792
/**
@@ -85,9 +100,23 @@ public CMapEncoding(String cmap, String uniMap) {
85100
if (cmap.equals(PdfEncodings.IDENTITY_H) || cmap.equals(PdfEncodings.IDENTITY_V)) {
86101
cid2Uni = FontCache.getCid2UniCmap(uniMap);
87102
isDirect = true;
103+
this.codeSpaceRanges = IDENTITY_H_V_CODESPACE_RANGES;
88104
} else {
89105
cid2Code = FontCache.getCid2Byte(cmap);
90106
code2Cid = cid2Code.getReversMap();
107+
this.codeSpaceRanges = cid2Code.getCodeSpaceRanges();
108+
}
109+
}
110+
111+
public CMapEncoding(String cmap, byte[] cmapBytes) {
112+
this.cmap = cmap;
113+
cid2Code = new CMapCidByte();
114+
try {
115+
CMapParser.parseCid(cmap, cid2Code, new CMapLocationFromBytes(cmapBytes));
116+
code2Cid = cid2Code.getReversMap();
117+
this.codeSpaceRanges = cid2Code.getCodeSpaceRanges();
118+
} catch (IOException e) {
119+
LoggerFactory.getLogger(getClass()).error(LogMessageConstant.FAILED_TO_PARSE_ENCODING_STREAM);
91120
}
92121
}
93122

@@ -131,6 +160,11 @@ public String getCmapName() {
131160
return cmap;
132161
}
133162

163+
164+
/**
165+
* @deprecated Will be removed in 7.2. Use {@link #getCmapBytes(int)} instead.
166+
*/
167+
@Deprecated
134168
public int getCmapCode(int cid) {
135169
if (isDirect) {
136170
return cid;
@@ -139,6 +173,44 @@ public int getCmapCode(int cid) {
139173
}
140174
}
141175

176+
public byte[] getCmapBytes(int cid) {
177+
int length = getCmapBytesLength(cid);
178+
byte[] result = new byte[length];
179+
fillCmapBytes(cid, result, 0);
180+
return result;
181+
}
182+
183+
public int fillCmapBytes(int cid, byte[] array, int offset) {
184+
if (isDirect) {
185+
array[offset++] = (byte)((cid & 0xff00) >> 8);
186+
array[offset++] = (byte)(cid & 0xff);
187+
} else {
188+
byte[] bytes = cid2Code.lookup(cid);
189+
for (int i = 0; i < bytes.length; i++) {
190+
array[offset++] = bytes[i];
191+
}
192+
}
193+
return offset;
194+
}
195+
196+
public void fillCmapBytes(int cid, ByteBuffer buffer) {
197+
if (isDirect) {
198+
buffer.append((byte)((cid & 0xff00) >> 8));
199+
buffer.append((byte)(cid & 0xff));
200+
} else {
201+
byte[] bytes = cid2Code.lookup(cid);
202+
buffer.append(bytes);
203+
}
204+
}
205+
206+
public int getCmapBytesLength(int cid) {
207+
if (isDirect) {
208+
return 2;
209+
} else {
210+
return cid2Code.lookup(cid).length;
211+
}
212+
}
213+
142214
public int getCidCode(int cmapCode) {
143215
if (isDirect) {
144216
return cmapCode;
@@ -147,6 +219,28 @@ public int getCidCode(int cmapCode) {
147219
}
148220
}
149221

222+
public boolean containsCodeInCodeSpaceRange(int code, int length) {
223+
for (int i = 0; i < codeSpaceRanges.size(); i += 2) {
224+
if (length == codeSpaceRanges.get(i).length) {
225+
int mask = 0xff;
226+
int totalShift = 0;
227+
byte[] low = codeSpaceRanges.get(i);
228+
byte[] high = codeSpaceRanges.get(i + 1);
229+
boolean fitsIntoRange = true;
230+
for (int ind = length - 1; ind >= 0; ind--, totalShift += 8, mask <<= 8) {
231+
int actualByteValue = (code & mask) >> totalShift;
232+
if (!(actualByteValue >= (0xff & low[ind]) && actualByteValue <= (0xff & high[ind]))) {
233+
fitsIntoRange = false;
234+
}
235+
}
236+
if (fitsIntoRange) {
237+
return true;
238+
}
239+
}
240+
}
241+
return false;
242+
}
243+
150244
private static int toInteger(byte[] bytes) {
151245
int result = 0;
152246
for (byte b : bytes) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ void setSupplement(int supplement) {
9494
}
9595

9696
abstract void addChar(String mark, CMapObject code);
97+
98+
void addCodeSpaceRange(byte[] low, byte[] high) {
99+
}
97100

98101
void addRange(String from, String to, CMapObject code) {
99102
byte[] a1 = decodeStringToByte(from);

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ This file is part of the iText (R) project.
4545

4646
import com.itextpdf.io.util.IntHashtable;
4747

48+
import java.util.ArrayList;
4849
import java.util.HashMap;
50+
import java.util.List;
4951
import java.util.Map;
5052

5153
/**
@@ -56,6 +58,7 @@ public class CMapCidByte extends AbstractCMap {
5658
private static final long serialVersionUID = 4956059671207068672L;
5759
private Map<Integer, byte[]> map = new HashMap<>();
5860
private final byte[] EMPTY = {};
61+
private List<byte[]> codeSpaceRanges = new ArrayList<>();
5962

6063
@Override
6164
void addChar(String mark, CMapObject code) {
@@ -87,4 +90,18 @@ public IntHashtable getReversMap() {
8790
}
8891
return code2cid;
8992
}
93+
94+
/**
95+
* Returns a list containing sequential pairs of code space beginning and endings:
96+
* (begincodespacerange1, endcodespacerange1, begincodespacerange2, endcodespacerange1, ...)
97+
*/
98+
public List<byte[]> getCodeSpaceRanges() {
99+
return codeSpaceRanges;
100+
}
101+
102+
@Override
103+
void addCodeSpaceRange(byte[] low, byte[] high) {
104+
codeSpaceRanges.add(low);
105+
codeSpaceRanges.add(high);
106+
}
90107
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,12 @@ public String toString() {
121121
}
122122
return value.toString();
123123
}
124+
125+
public byte[] toHexByteArray() {
126+
if (type == HEX_STRING) {
127+
return (byte[])value;
128+
} else {
129+
return null;
130+
}
131+
}
124132
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,20 @@ This file is part of the iText (R) project.
4545

4646
import com.itextpdf.io.LogMessageConstant;
4747
import com.itextpdf.io.source.PdfTokenizer;
48+
import org.slf4j.Logger;
49+
import org.slf4j.LoggerFactory;
4850

4951
import java.util.ArrayList;
5052
import java.util.List;
5153

52-
import org.slf4j.Logger;
53-
import org.slf4j.LoggerFactory;
54-
5554
public class CMapParser {
5655

5756
private static final String def = "def";
5857
private static final String endcidrange = "endcidrange";
5958
private static final String endcidchar = "endcidchar";
6059
private static final String endbfrange = "endbfrange";
6160
private static final String endbfchar = "endbfchar";
61+
private static final String endcodespacerange = "endcodespacerange";
6262
private static final String usecmap = "usecmap";
6363

6464
private static final String Registry = "Registry";
@@ -121,6 +121,14 @@ private static void parseCid(String cmapName, AbstractCMap cmap, ICMapLocation l
121121
}
122122
} else if (last.equals(usecmap) && list.size() == 2 && list.get(0).isName()) {
123123
parseCid(list.get(0).toString(), cmap, location, level + 1);
124+
} else if (last.equals(endcodespacerange)) {
125+
for (int i = 0; i < list.size() + 1; i += 2) {
126+
if (list.get(i).isHexString() && list.get(i + 1).isHexString()) {
127+
byte[] low = list.get(i).toHexByteArray();
128+
byte[] high = list.get(i + 1).toHexByteArray();
129+
cmap.addCodeSpaceRange(low, high);
130+
}
131+
}
124132
}
125133
}
126134
} catch (Exception ex) {

0 commit comments

Comments
 (0)