Skip to content

Commit 4741f65

Browse files
Add CFF CIDFont support
In this case CIDs are not equal to GIDs in font processing. DEVSIX-5767
1 parent 52e1c5a commit 4741f65

34 files changed

+723
-44
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,7 @@ public void xref() {
453453
offItem.set(this.myOffset-indexBase.myOffset+1);
454454
}
455455
}
456-
/**
457-
* TODO To change the template for this generated type comment go to
458-
* Window - Preferences - Java - Code Generation - Code and Comments
459-
*/
456+
460457
protected static final class SubrMarkerItem extends Item {
461458
private OffsetItem offItem;
462459
private IndexBaseItem indexBase;
@@ -951,6 +948,9 @@ public byte[] getCID(String fontName)
951948
return b;
952949
}
953950

951+
public boolean isCID() {
952+
return isCID(getNames()[0]);
953+
}
954954

955955
public boolean isCID(String fontName) {
956956
int j;
@@ -1024,6 +1024,8 @@ protected final class Font {
10241024
public int[] PrivateSubrsOffset;
10251025
public int[][] PrivateSubrsOffsetsArray;
10261026
public int[] SubrsOffsets;
1027+
1028+
public int[] gidToCid;
10271029
}
10281030
// Changed from private to protected by Ygal&Oren
10291031
protected Font[] fonts;

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

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ This file is part of the iText (R) project.
4848
import com.itextpdf.io.util.GenericArray;
4949

5050
import java.util.ArrayList;
51+
import java.util.Collections;
5152
import java.util.HashSet;
5253
import java.util.LinkedList;
5354
import java.util.List;
@@ -166,9 +167,16 @@ public class CFFFontSubset extends CFFFont {
166167
* C'tor for CFFFontSubset
167168
*
168169
* @param cff - The font file
169-
* @param GlyphsUsed - a Map that contains the glyph used in the subset
170170
*/
171+
CFFFontSubset(byte[] cff) {
172+
this(cff, Collections.<Integer>emptySet(), true);
173+
}
174+
171175
public CFFFontSubset(byte[] cff, Set<Integer> GlyphsUsed) {
176+
this(cff, GlyphsUsed, false);
177+
}
178+
179+
CFFFontSubset(byte[] cff, Set<Integer> GlyphsUsed, boolean isCidParsingRequired) {
172180
// Use CFFFont c'tor in order to parse the font file.
173181
super(cff);
174182
this.GlyphsUsed = GlyphsUsed;
@@ -187,6 +195,10 @@ public CFFFontSubset(byte[] cff, Set<Integer> GlyphsUsed) {
187195
// For each font save the offset array of the charstring
188196
fonts[i].charstringsOffsets = getIndex(fonts[i].charstringsOffset);
189197

198+
if (isCidParsingRequired) {
199+
initGlyphIdToCharacterIdArray(i, fonts[i].nglyphs, fonts[i].charsetOffset);
200+
}
201+
190202
// Process the FDSelect if exist
191203
if (fonts[i].fdselectOffset >= 0) {
192204
// Process the FDSelect
@@ -535,8 +547,8 @@ protected void BuildSubrUsed(int Font, int FD, int SubrOffset, int[] SubrsOffset
535547
int LBias = CalcBias(SubrOffset, Font);
536548

537549
// For each glyph used find its GID, start & end pos
538-
for (int i = 0; i < glyphsInList.size(); i++) {
539-
int glyph = (int) glyphsInList.get(i);
550+
for (Integer usedGlyph : glyphsInList) {
551+
int glyph = (int) usedGlyph;
540552
int Start = fonts[Font].charstringsOffsets[glyph];
541553
int End = fonts[Font].charstringsOffsets[glyph + 1];
542554

@@ -1704,5 +1716,77 @@ void CreateNonCIDSubrs(int Font, IndexBaseItem PrivateBase, OffsetItem Subrs) {
17041716
OutputList.addLast(new RangeItem(new RandomAccessFileOrArray(rasFactory.createSource(NewSubrsIndexNonCID)), 0, NewSubrsIndexNonCID.length));
17051717
}
17061718
}
1719+
1720+
/**
1721+
* Returns the CID to which specified GID is mapped.
1722+
*
1723+
* @param gid glyph identifier
1724+
*
1725+
* @return CID value
1726+
*/
1727+
int getCidForGlyphId(int gid) {
1728+
return getCidForGlyphId(0, gid);
1729+
}
1730+
1731+
/**
1732+
* Returns the CID to which specified GID is mapped.
1733+
*
1734+
* @param fontIndex index of font for which cid-gid mapping is to be identified
1735+
* @param gid glyph identifier
1736+
*
1737+
* @return CID value
1738+
*/
1739+
int getCidForGlyphId(int fontIndex, int gid) {
1740+
if (fonts[fontIndex].gidToCid == null) {
1741+
return gid;
1742+
}
1743+
1744+
// gidToCid mapping starts with value corresponding to gid == 1, becuase .notdef is omitted
1745+
int index = gid - 1;
1746+
return index >= 0 && index < fonts[fontIndex].gidToCid.length
1747+
? fonts[fontIndex].gidToCid[index]
1748+
: gid;
1749+
}
1750+
1751+
/**
1752+
* Creates glyph-to-character id array.
1753+
*
1754+
* @param fontIndex index of font for which charsets data is to be parsed
1755+
* @param numOfGlyphs number of glyphs in the font
1756+
* @param offset the offset to charsets data
1757+
*/
1758+
private void initGlyphIdToCharacterIdArray(int fontIndex, int numOfGlyphs, int offset) {
1759+
// Seek charset offset
1760+
seek(offset);
1761+
1762+
// Read the format
1763+
int format = getCard8();
1764+
1765+
// .notdef is omitted, therefore remaining number of elements is one less than overall number
1766+
int numOfElements = numOfGlyphs - 1;
1767+
fonts[fontIndex].gidToCid = new int[numOfElements];
1768+
1769+
switch (format) {
1770+
case 0:
1771+
for (int i = 0; i < numOfElements; i++) {
1772+
int cid = getCard16();
1773+
fonts[fontIndex].gidToCid[i] = cid;
1774+
}
1775+
break;
1776+
case 1:
1777+
case 2:
1778+
int start = 0;
1779+
while (start < numOfElements) {
1780+
int first = getCard16();
1781+
int nLeft = format == 1 ? getCard8() : getCard16();
1782+
for (int i = 0; i <= nLeft && start < numOfElements; i++) {
1783+
fonts[fontIndex].gidToCid[start++] = first + i;
1784+
}
1785+
}
1786+
break;
1787+
default:
1788+
break;
1789+
}
1790+
}
17071791
}
17081792

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2022 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.io.font;
24+
25+
import com.itextpdf.io.font.otf.Glyph;
26+
27+
class GidAwareGlyph extends Glyph {
28+
private int gid;
29+
30+
public GidAwareGlyph(int code, int width, int unicode, int[] bbox) {
31+
super(code, width, unicode, bbox);
32+
}
33+
34+
public void setGid(int index) {
35+
this.gid = index;
36+
}
37+
38+
public int getGid() {
39+
return gid;
40+
}
41+
}

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

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

46+
import com.itextpdf.commons.utils.MessageFormatUtil;
4647
import com.itextpdf.io.exceptions.IOException;
47-
import com.itextpdf.io.logs.IoLogMessageConstant;
4848
import com.itextpdf.io.font.constants.TrueTypeCodePages;
4949
import com.itextpdf.io.font.otf.Glyph;
5050
import com.itextpdf.io.font.otf.GlyphPositioningTableReader;
5151
import com.itextpdf.io.font.otf.GlyphSubstitutionTableReader;
5252
import com.itextpdf.io.font.otf.OpenTypeGdefTableReader;
53+
import com.itextpdf.io.logs.IoLogMessageConstant;
5354
import com.itextpdf.io.util.IntHashtable;
54-
import com.itextpdf.commons.utils.MessageFormatUtil;
5555

5656
import java.util.ArrayList;
5757
import java.util.LinkedHashMap;
@@ -60,6 +60,7 @@ This file is part of the iText (R) project.
6060
import java.util.Objects;
6161
import java.util.Set;
6262
import java.util.SortedSet;
63+
import java.util.stream.Collectors;
6364
import org.slf4j.Logger;
6465
import org.slf4j.LoggerFactory;
6566

@@ -214,6 +215,29 @@ public byte[] getSubset(Set<Integer> glyphs, boolean subset) {
214215
}
215216
}
216217

218+
/**
219+
* Maps a set of glyph CIDs (as used in PDF file) to corresponding GID values
220+
* (as a glyph primary identifier in the font file).
221+
* This call is only meaningful for fonts that return true for {@link #isCff()}.
222+
* For other types of fonts, GID and CID are always the same, so that call would essentially
223+
* return a set of the same values.
224+
*
225+
* @param glyphs a set of glyph CIDs
226+
*
227+
* @return a set of glyph ids corresponding to the passed glyph CIDs
228+
*/
229+
public Set<Integer> mapGlyphsCidsToGids(Set<Integer> glyphs) {
230+
return glyphs.stream()
231+
.map((Integer i) -> {
232+
Glyph usedGlyph = getGlyphByCode(i);
233+
if (usedGlyph instanceof GidAwareGlyph) {
234+
return ((GidAwareGlyph) usedGlyph).getGid();
235+
}
236+
return i;
237+
})
238+
.collect(Collectors.toSet());
239+
}
240+
217241
protected void readGdefTable() throws java.io.IOException {
218242
int[] gdef = fontParser.tables.get("GDEF");
219243
if (gdef != null) {
@@ -299,6 +323,10 @@ private void initializeFontProperties() throws java.io.IOException {
299323
unicodeToGlyph = new LinkedHashMap<>(cmap.size());
300324
codeToGlyph = new LinkedHashMap<>(numOfGlyphs);
301325
avgWidth = 0;
326+
CFFFontSubset cffFontSubset = null;
327+
if (isCff()) {
328+
cffFontSubset = new CFFFontSubset(getFontStreamBytes());
329+
}
302330
for (int charCode : cmap.keySet()) {
303331
int index = cmap.get(charCode)[0];
304332
if (index >= numOfGlyphs) {
@@ -307,12 +335,24 @@ private void initializeFontProperties() throws java.io.IOException {
307335
getFontNames().getFontName(), index));
308336
continue;
309337
}
310-
Glyph glyph = new Glyph(index, glyphWidths[index], charCode, bBoxes != null ? bBoxes[index] : null);
338+
int cid;
339+
Glyph glyph;
340+
int[] glyphBBox = bBoxes != null ? bBoxes[index] : null;
341+
if (cffFontSubset != null && cffFontSubset.isCID()) {
342+
cid = cffFontSubset.getCidForGlyphId(index);
343+
GidAwareGlyph cffGlyph = new GidAwareGlyph(cid, glyphWidths[index], charCode, glyphBBox);
344+
cffGlyph.setGid(index);
345+
glyph = cffGlyph;
346+
} else {
347+
cid = index;
348+
glyph = new Glyph(cid, glyphWidths[index], charCode, glyphBBox);
349+
}
350+
311351
unicodeToGlyph.put(charCode, glyph);
312352
// This is done on purpose to keep the mapping to glyphs with smaller unicode values, in contrast with
313353
// larger values which often represent different forms of other characters.
314-
if (!codeToGlyph.containsKey(index)) {
315-
codeToGlyph.put(index, glyph);
354+
if (!codeToGlyph.containsKey(cid)) {
355+
codeToGlyph.put(cid, glyph);
316356
}
317357
avgWidth += glyph.getWidth();
318358
}

io/src/main/java/com/itextpdf/io/font/otf/Glyph.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@
4949
import java.util.Arrays;
5050

5151
public class Glyph {
52-
53-
5452
private static final char REPLACEMENT_CHARACTER = '\ufffd';
5553
private static final char[] REPLACEMENT_CHARACTERS = new char[] {REPLACEMENT_CHARACTER};
5654
private static final String REPLACEMENT_CHARACTER_STRING = String.valueOf(REPLACEMENT_CHARACTER);

0 commit comments

Comments
 (0)