/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks. Copyright (C) 2002-2020 by Jin-Hwan Cho and Shunsaku Hirata, the dvipdfmx project team. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* * A large part of codes are brought from ttfdump-0.5.5. */ #ifdef HAVE_CONFIG_H #include #endif #include "system.h" #include "mem.h" #include "error.h" #include "dpxconf.h" #include "sfnt.h" /* Sorry for placing this here. * We need to rewrite TrueType font support code... */ #include "cmap.h" #include "cmap_write.h" #include "tt_aux.h" #include "tt_gsub.h" #include "tt_post.h" #include "unicode.h" #include "agl.h" #include "pdfresource.h" #include "dpxfile.h" /* Hash */ #include "dpxutil.h" #include "tt_cmap.h" #define VERBOSE_LEVEL_MIN 0 /* format 0: byte encoding table */ struct cmap0 { BYTE glyphIndexArray[256]; }; static struct cmap0 * read_cmap0 (sfnt *sfont, ULONG len) { struct cmap0 *map; int i; if (len < 256) { WARN("invalid format 0 TT cmap subtable"); return NULL; } map = NEW(1, struct cmap0); for (i = 0; i < 256; i++) map->glyphIndexArray[i] = sfnt_get_byte(sfont); return map; } static void release_cmap0(struct cmap0 *map) { if (map) RELEASE(map); } static USHORT lookup_cmap0 (struct cmap0 *map, USHORT cc) { return ((cc > 255) ? 0 : map->glyphIndexArray[cc]); } /* format 2: high-byte mapping through table */ struct SubHeader { USHORT firstCode; USHORT entryCount; SHORT idDelta; USHORT idRangeOffset; }; struct cmap2 { USHORT subHeaderKeys[256]; struct SubHeader *subHeaders; USHORT *glyphIndexArray; }; static struct cmap2 * read_cmap2 (sfnt *sfont, ULONG len) { struct cmap2 *map; USHORT i, n; if (len < 512) { WARN("invalid fromt2 TT cmap subtable"); return NULL; } map = NEW(1, struct cmap2); for (i = 0; i < 256; i++) map->subHeaderKeys[i] = sfnt_get_ushort(sfont); for (n = 0, i = 0; i < 256; i++) { map->subHeaderKeys[i] /= 8; if (n < map->subHeaderKeys[i]) n = map->subHeaderKeys[i]; } n += 1; /* the number of subHeaders is one plus the max of subHeaderKeys */ if (len < 512 + n * 8 ) { WARN("invalid/truncated format2 TT cmap subtable"); RELEASE(map); return NULL; } map->subHeaders = NEW(n, struct SubHeader); for (i = 0; i < n; i++) { map->subHeaders[i].firstCode = sfnt_get_ushort(sfont); map->subHeaders[i].entryCount = sfnt_get_ushort(sfont); map->subHeaders[i].idDelta = sfnt_get_short(sfont); map->subHeaders[i].idRangeOffset = sfnt_get_ushort(sfont); /* It makes things easier to let the offset starts from * the beginning of glyphIndexArray. */ if (map->subHeaders[i].idRangeOffset != 0) map->subHeaders[i].idRangeOffset -= (2 + (n - i - 1) * 8); } /* Caculate the length of glyphIndexArray, this is ugly, * there should be a better way to get this information. */ n = (USHORT) (len - 518 - n * 8) / 2; map->glyphIndexArray = NEW(n, USHORT); for (i = 0; i < n; i++) map->glyphIndexArray[i] = sfnt_get_ushort(sfont); return map; } static void release_cmap2 (struct cmap2 *map) { if (map) { if (map->subHeaders) RELEASE(map->subHeaders); if (map->glyphIndexArray) RELEASE(map->glyphIndexArray); RELEASE(map); } } static USHORT lookup_cmap2 (struct cmap2 *map, USHORT cc) { USHORT idx = 0; SHORT idDelta; USHORT firstCode, entryCount, idRangeOffset; int hi, lo; USHORT i; hi = (cc >> 8) & 0xff; lo = cc & 0xff; /* select which subHeader to use */ i = map->subHeaderKeys[hi]; firstCode = map->subHeaders[i].firstCode; entryCount = map->subHeaders[i].entryCount; idDelta = map->subHeaders[i].idDelta; idRangeOffset = map->subHeaders[i].idRangeOffset / 2; if (lo >= firstCode && lo < firstCode + entryCount) { idRangeOffset += lo - firstCode; idx = map->glyphIndexArray[idRangeOffset]; if (idx != 0) idx = (idx + idDelta) & 0xffff; } return idx; } /* * format 4: segment mapping to delta values * - Microsoft standard character to glyph index mapping table */ struct cmap4 { USHORT segCountX2; USHORT searchRange; USHORT entrySelector; USHORT rangeShift; USHORT *endCount; USHORT reservedPad; USHORT *startCount; USHORT *idDelta; USHORT *idRangeOffset; USHORT *glyphIndexArray; }; static struct cmap4 * read_cmap4(sfnt *sfont, ULONG len) { struct cmap4 *map; USHORT i, n, segCount; if (len < 8) { WARN("invalid format 4 TT cmap subtable"); return NULL; } map = NEW(1, struct cmap4); map->segCountX2 = segCount = sfnt_get_ushort(sfont); map->searchRange = sfnt_get_ushort(sfont); map->entrySelector = sfnt_get_ushort(sfont); map->rangeShift = sfnt_get_ushort(sfont); segCount /= 2; map->endCount = NEW(segCount, USHORT); for (i = 0; i < segCount; i++) map->endCount[i] = sfnt_get_ushort(sfont); map->reservedPad = sfnt_get_ushort(sfont); map->startCount = NEW(segCount, USHORT); for (i = 0; i < segCount; i++) map->startCount[i] = sfnt_get_ushort(sfont); map->idDelta = NEW(segCount, USHORT); for (i = 0; i < segCount; i++) map->idDelta[i] = sfnt_get_ushort(sfont); map->idRangeOffset = NEW(segCount, USHORT); for (i = 0; i < segCount; i++) map->idRangeOffset[i] = sfnt_get_ushort(sfont); n = (len - 16 - 8 * segCount) / 2; if (n == 0) map->glyphIndexArray = NULL; else { map->glyphIndexArray = NEW(n, USHORT); for (i = 0; i < n; i++) map->glyphIndexArray[i] = sfnt_get_ushort(sfont); } return map; } static void release_cmap4 (struct cmap4 *map) { if (map) { if (map->endCount) RELEASE(map->endCount); if (map->startCount) RELEASE(map->startCount); if (map->idDelta) RELEASE(map->idDelta); if (map->idRangeOffset) RELEASE(map->idRangeOffset); if (map->glyphIndexArray) RELEASE(map->glyphIndexArray); RELEASE(map); } } static USHORT lookup_cmap4 (struct cmap4 *map, USHORT cc) { USHORT gid = 0; USHORT i, j, segCount; /* * Segments are sorted in order of increasing endCode values. * Last segment maps 0xffff to gid 0 (?) */ i = segCount = map->segCountX2 / 2; while (i-- > 0 && cc <= map->endCount[i]) { if (cc >= map->startCount[i]) { if (map->idRangeOffset[i] == 0) { gid = (cc + map->idDelta[i]) & 0xffff; } else if (cc == 0xffff && map->idRangeOffset[i] == 0xffff) { /* this is for protection against some old broken fonts... */ gid = 0; } else { j = map->idRangeOffset[i] - (segCount - i) * 2; j = (cc - map->startCount[i]) + (j / 2); gid = map->glyphIndexArray[j]; if (gid != 0) gid = (gid + map->idDelta[i]) & 0xffff; } break; } } return gid; } /* format 6: trimmed table mapping */ struct cmap6 { USHORT firstCode; USHORT entryCount; USHORT *glyphIndexArray; }; static struct cmap6 * read_cmap6 (sfnt *sfont, ULONG len) { struct cmap6 *map; USHORT i; if (len < 4) { WARN("invalid format 6 TT cmap subtable"); return NULL; } map = NEW(1, struct cmap6); map->firstCode = sfnt_get_ushort(sfont); map->entryCount = sfnt_get_ushort(sfont); map->glyphIndexArray = NEW(map->entryCount, USHORT); for (i = 0; i < map->entryCount; i++) map->glyphIndexArray[i] = sfnt_get_ushort(sfont); return map; } static void release_cmap6 (struct cmap6 *map) { if (map) { if (map->glyphIndexArray) RELEASE(map->glyphIndexArray); RELEASE(map); } } static USHORT lookup_cmap6 (struct cmap6 *map, USHORT cc) { USHORT idx; idx = cc - map->firstCode; if (idx < map->entryCount) return map->glyphIndexArray[idx]; return 0; } /* Format 8 and 10 not supported... * * format 8: mixed 16-bit and 32-bit coverage * format 10: trimmed array */ /* * format 12: segmented coverage * * startGlyphID is 32-bit long, however, GlyphID is still 16-bit long ! */ struct charGroup { ULONG startCharCode; ULONG endCharCode; ULONG startGlyphID; }; struct cmap12 { ULONG nGroups; struct charGroup *groups; }; /* ULONG length */ static struct cmap12 * read_cmap12 (sfnt *sfont, ULONG len) { struct cmap12 *map; ULONG i; if (len < 4) { WARN("invalid format 12 TT cmap subtable"); return NULL; } map = NEW(1, struct cmap12); map->nGroups = sfnt_get_ulong(sfont); map->groups = NEW(map->nGroups, struct charGroup); for (i = 0; i < map->nGroups; i++) { map->groups[i].startCharCode = sfnt_get_ulong(sfont); map->groups[i].endCharCode = sfnt_get_ulong(sfont); map->groups[i].startGlyphID = sfnt_get_ulong(sfont); } return map; } static void release_cmap12 (struct cmap12 *map) { if (map) { if (map->groups) RELEASE(map->groups); RELEASE(map); } } static USHORT lookup_cmap12 (struct cmap12 *map, ULONG cccc) { USHORT gid = 0; int i; i = map->nGroups; while (i-- > 0 && cccc <= map->groups[i].endCharCode) { if (cccc >= map->groups[i].startCharCode) { gid = (USHORT) ((cccc - map->groups[i].startCharCode + map->groups[i].startGlyphID) & 0xffff); break; } } return gid; } /* read cmap */ tt_cmap * tt_cmap_read (sfnt *sfont, USHORT platform, USHORT encoding) { tt_cmap *cmap = NULL; ULONG offset, length = 0; USHORT p_id, e_id; USHORT i, n_subtabs; ASSERT(sfont); offset = sfnt_locate_table(sfont, "cmap"); (void) sfnt_get_ushort(sfont); n_subtabs = sfnt_get_ushort(sfont); for (i = 0; i < n_subtabs; i++) { p_id = sfnt_get_ushort(sfont); e_id = sfnt_get_ushort(sfont); if (p_id != platform || e_id != encoding) sfnt_get_ulong(sfont); else { offset += sfnt_get_ulong(sfont); break; } } if (i == n_subtabs) return NULL; cmap = NEW(1, tt_cmap); cmap->map = NULL; cmap->platform = platform; cmap->encoding = encoding; sfnt_seek_set(sfont, offset); cmap->format = sfnt_get_ushort(sfont); /* Length and version (language) is ULONG for * format 8, 10, 12 ! */ if (cmap->format <= 6) { length = sfnt_get_ushort(sfont); cmap->language = sfnt_get_ushort(sfont); /* language (Mac) */ } else { if (sfnt_get_ushort(sfont) != 0) { /* reverved - 0 */ WARN("Unrecognized cmap subtable format."); tt_cmap_release(cmap); return NULL; } else { length = sfnt_get_ulong(sfont); cmap->language = sfnt_get_ulong(sfont); } } switch(cmap->format) { case 0: cmap->map = read_cmap0(sfont, length); break; case 2: cmap->map = read_cmap2(sfont, length); break; case 4: cmap->map = read_cmap4(sfont, length); break; case 6: cmap->map = read_cmap6(sfont, length); break; case 12: /* WARN("UCS-4 TrueType cmap table..."); */ cmap->map = read_cmap12(sfont, length); break; default: WARN("Unrecognized OpenType/TrueType cmap format."); tt_cmap_release(cmap); return NULL; break; } if (!cmap->map) { tt_cmap_release(cmap); cmap = NULL; } return cmap; } void tt_cmap_release (tt_cmap *cmap) { if (cmap) { if (cmap->map) { switch (cmap->format) { case 0: release_cmap0(cmap->map); break; case 2: release_cmap2(cmap->map); break; case 4: release_cmap4(cmap->map); break; case 6: release_cmap6(cmap->map); break; case 12: release_cmap12(cmap->map); break; default: WARN("Unrecognized OpenType/TrueType cmap format: %d", cmap->format); break; } } RELEASE(cmap); } return; } USHORT tt_cmap_lookup (tt_cmap *cmap, ULONG cc) { USHORT gid = 0; ASSERT(cmap); if (cc > 0xffffL && cmap->format < 12) { WARN("Four bytes charcode not supported in OpenType/TrueType cmap format 0...6."); return 0; } switch (cmap->format) { case 0: gid = lookup_cmap0(cmap->map, (USHORT) cc); break; case 2: gid = lookup_cmap2(cmap->map, (USHORT) cc); break; case 4: gid = lookup_cmap4(cmap->map, (USHORT) cc); break; case 6: gid = lookup_cmap6(cmap->map, (USHORT) cc); break; case 12: gid = lookup_cmap12(cmap->map, (ULONG) cc); break; default: WARN("Unrecognized OpenType/TrueType cmap subtable format: %d", cmap->format); break; } return gid; } static unsigned char srange_min[2] = {0x00, 0x00}; static unsigned char srange_max[2] = {0xff, 0xff}; static unsigned char lrange_min[4] = {0x00, 0x00, 0x00, 0x00}; static unsigned char lrange_max[4] = {0x7f, 0xff, 0xff, 0xff}; /* OpenType CIDFont: * * We don't use GID for them. OpenType cmap table is for * charcode to GID mapping rather than to-CID mapping. */ #include "cid.h" #include "tt_table.h" #include "cff_types.h" #include "cff_dict.h" #include "cff.h" /* This should be moved to cff.c */ static void create_GIDToCIDMap (uint16_t *GIDToCIDMap, uint16_t num_glyphs, cff_font *cffont) { cff_charsets *charset; uint16_t gid, i; ASSERT(GIDToCIDMap); if (!cffont || !(cffont->flag & FONTTYPE_CIDFONT)) { for (gid = 0; gid < num_glyphs; gid++) { GIDToCIDMap[gid] = gid; } return; } memset(GIDToCIDMap, 0, num_glyphs*sizeof(uint16_t)); charset = cffont->charsets; if (!charset) return; switch (charset->format) { case 0: { s_SID *cids; /* CID... */ cids = charset->data.glyphs; for (gid = 1, i = 0; i < charset->num_entries; i++) { GIDToCIDMap[gid] = cids[i]; gid++; } } break; case 1: { cff_range1 *ranges; card16 cid, count; ranges = charset->data.range1; for (gid = 1, i = 0; i < charset->num_entries; i++) { cid = ranges[i].first; count = ranges[i].n_left + 1; /* card8 */ while (count-- > 0 && gid <= num_glyphs) { GIDToCIDMap[gid] = cid; gid++; cid++; } } } break; case 2: { cff_range2 *ranges; card16 cid, count; ranges = charset->data.range2; if (charset->num_entries == 1 && ranges[0].first == 1) { /* "Complete" CIDFont */ for (gid = 0; gid < num_glyphs; gid++) { GIDToCIDMap[gid] = gid; } } else { /* Not trivial mapping */ for (gid = 1, i = 0; i < charset->num_entries; i++) { cid = ranges[i].first; count = ranges[i].n_left + 1; while (count-- > 0 && gid <= num_glyphs) { GIDToCIDMap[gid] = cid; gid++; cid++; } } } } break; default: WARN("Unknown CFF charset format...: %d", charset->format); break; } return; } /* Soft-hyphen (U+00AD) to lower its priority... added here for convenience */ static int is_PUA_or_presentation (unsigned int uni) { /* Some of CJK Radicals Supplement and Kangxi Radicals * are commonly double encoded, lower the priority. * CJK Compatibility Ideographs & Supplement added. */ return ((uni >= 0x2E80 && uni <= 0x2EF3) || (uni >= 0x2F00 && uni <= 0x2FD5) || (uni >= 0xE000 && uni <= 0xF8FF) || (uni >= 0xFB00 && uni <= 0xFB4F) || (uni >= 0xF900 && uni <= 0xFAFF) || (uni >= 0x2F800 && uni <= 0x2FA1F) || (uni >= 0xF0000 && uni <= 0xFFFFD) || (uni >= 0x100000 && uni <= 0x10FFFD) || (uni == 0x00AD)); } static char * lookup_glyph_name (struct tt_post_table *post, cff_font *cffont, USHORT gid) { char *name = NULL; if (post) name = tt_get_glyphname(post, gid); if (!name && cffont) name = cff_get_glyphname(cffont, gid); return name; } /* * Substituted glyphs: * * Mapping information stored in cmap_add. */ #ifndef is_used_char2 #define is_used_char2(b,c) (((b)[(c)/8]) & (1 << (7-((c)%8)))) #endif static int32_t handle_subst_glyphs (CMap *cmap, CMap *cmap_add, char *used_chars) { int32_t count = 0; int32_t cid; for (cid = 0; cid < 65536; cid++) { if (!is_used_char2(used_chars, cid)) continue; else { unsigned char buf[256]; int inbytesleft = 2, outbytesleft = 254; size_t len; unsigned char *outbuf = buf + 2; const unsigned char *inbuf = buf; buf[0] = (cid >> 8) & 0xff; buf[1] = cid & 0xff; CMap_decode(cmap_add, &inbuf, &inbytesleft, &outbuf, &outbytesleft); if (inbytesleft == 0) { len = 254 - outbytesleft; CMap_add_bfchar(cmap, buf, 2, buf + 2, len); used_chars[cid / 8] &= ~(1 << (7 - (cid % 8))); count++; } } } return count; } static int32_t add_ToUnicode_via_glyph_name (CMap *cmap, char *used_chars, USHORT num_glyphs, uint16_t *GIDToCIDMap, sfnt *sfont, cff_font *cffont) { int32_t count = 0; USHORT gid; struct tt_post_table *post = NULL; post = tt_read_post_table(sfont); if (!post && !cffont) return count; for (gid = 0; gid < num_glyphs; gid++) { uint16_t cid = GIDToCIDMap[gid]; if (is_used_char2(used_chars, cid)) { #define MAX_UNICODES 32 char *name; int32_t unicodes[MAX_UNICODES]; int unicode_count = -1; name = lookup_glyph_name(post, cffont, gid); if (name) { unicode_count = agl_get_unicodes(name, unicodes, MAX_UNICODES); #undef MAX_UNICODES RELEASE(name); if (unicode_count > 0) { unsigned char *buf; unsigned char *p, *endptr; int k; size_t len = 0; buf = NEW(unicode_count*4+2, unsigned char); p = buf + 2; endptr = buf + (unicode_count * 4 + 2); for (k = 0; k < unicode_count; ++k) { len += UC_UTF16BE_encode_char(unicodes[k], &p, endptr); } buf[0] = (cid >> 8) & 0xff; buf[1] = cid & 0xff; CMap_add_bfchar(cmap, buf, 2, buf + 2, len); used_chars[cid / 8] &= ~(1 << (7 - (cid % 8))); count++; RELEASE(buf); } } } } if (post) tt_release_post_table(post); return count; } static void create_inverse_cmap4 (int32_t *map_base, int32_t *map_sub, USHORT num_glyphs, struct cmap4 *map) { USHORT segCount = map->segCountX2 / 2; USHORT i, j; for (i = 0; i < segCount; i++) { USHORT c0 = map->startCount[i]; USHORT c1 = map->endCount[i]; USHORT d = map->idRangeOffset[i] / 2 - (segCount - i); for (j = 0; j <= c1 - c0; j++) { USHORT ch = c0 + j; USHORT gid; if (map->idRangeOffset[i] == 0) { gid = (ch + map->idDelta[i]) & 0xffff; } else if (c0 == 0xffff && c1 == 0xffff && map->idRangeOffset[i] == 0xffff) { /* this is for protection against some old broken fonts... */ gid = 0; } else { gid = (map->glyphIndexArray[j + d] + map->idDelta[i]) & 0xffff; } if (is_PUA_or_presentation(ch)) { map_sub[gid] = ch; } else { map_base[gid] = ch; } } } } static void create_inverse_cmap12 (int32_t *map_base, int32_t *map_sub, USHORT num_glyphs, struct cmap12 *map) { ULONG i, ch; for (i = 0; i < map->nGroups; i++) { for (ch = map->groups[i].startCharCode; ch <= map->groups[i].endCharCode; ch++) { int d = ch - map->groups[i].startCharCode; USHORT gid = (USHORT) ((map->groups[i].startGlyphID + d) & 0xffff); if (is_PUA_or_presentation(ch)) { map_sub[gid] = ch; } else { map_base[gid] = ch; } } } } /* NOTE: Reverse mapping code which had been placed here is removed since: * - Implementation of reserve CMap mapping itself is imcomplete. * - It is wrong to assume that all CMap passed here is Unicode to CID mapping. * Especially, the second one causes problems. */ static pdf_obj * create_ToUnicode_cmap (tt_cmap *ttcmap, const char *cmap_name, CMap *cmap_add, const char *used_chars, sfnt *sfont) { pdf_obj *stream = NULL; int32_t *map_base = NULL, *map_sub = NULL; USHORT gid, num_glyphs = 0; ASSERT(ttcmap); /* Get num_glyphs from maxp talbe */ { struct tt_maxp_table *maxp; maxp = tt_read_maxp_table(sfont); if (maxp) { num_glyphs = maxp->numGlyphs; RELEASE(maxp); } } /* Initialize GID to Unicode mapping table */ map_base = NEW(num_glyphs, int32_t); map_sub = NEW(num_glyphs, int32_t); for (gid = 0; gid < num_glyphs; gid++) { map_base[gid] = -1; map_sub [gid] = -1; } /* Create "base" mapping from inverse mapping of OpenType cmap */ switch (ttcmap->format) { case 4: create_inverse_cmap4(map_base, map_sub, num_glyphs, ttcmap->map); break; case 12: create_inverse_cmap12(map_base, map_sub, num_glyphs, ttcmap->map); break; } /* Now create ToUnicode CMap stream */ { CMap *cmap; int32_t count; cff_font *cffont = NULL; char is_cidfont = 0; uint16_t *GIDToCIDMap = NULL; char *used_chars_copy = NULL; if (sfont->type == SFNT_TYPE_POSTSCRIPT) { ULONG offset; offset = sfnt_find_table_pos(sfont, "CFF "); /* "CFF " table must exist here. Just abort... */ if (offset == 0) { ERROR("\"CFF \" table not found. Must be found before... Can't continue."); } cffont = cff_open(sfont->stream, offset, 0); cff_read_charsets(cffont); } is_cidfont = cffont && (cffont->flag & FONTTYPE_CIDFONT); /* GIT to CID mapping info. */ GIDToCIDMap = NEW(num_glyphs, uint16_t); if (is_cidfont) { create_GIDToCIDMap(GIDToCIDMap, num_glyphs, cffont); } else { for (gid = 0; gid < num_glyphs; gid++) { GIDToCIDMap[gid] = gid; } } cmap = CMap_new(); CMap_set_name (cmap, cmap_name); CMap_set_wmode(cmap, 0); CMap_set_type (cmap, CMAP_TYPE_TO_UNICODE); CMap_set_CIDSysInfo(cmap, &CSI_UNICODE); CMap_add_codespacerange(cmap, srange_min, srange_max, 2); count = 0; used_chars_copy = NEW(8192, char); memcpy(used_chars_copy, used_chars, 8192); for (gid = 0; gid < num_glyphs; gid++) { uint16_t cid = GIDToCIDMap[gid]; if (is_used_char2(used_chars_copy, cid)) { int32_t ch; unsigned char src[2], dst[4]; unsigned char *p = dst, *endptr = dst + 4; size_t len; ch = map_base[gid]; if (UC_is_valid(ch)) { src[0] = (cid >> 8) & 0xff; src[1] = cid & 0xff; len = UC_UTF16BE_encode_char(ch, &p, endptr); CMap_add_bfchar(cmap, src, 2, dst, len); used_chars_copy[cid / 8] &= ~(1 << (7 - (cid % 8))); count++; } } } /* cmap_add here stores information about all unencoded glyphs which can be * accessed only through OT Layout GSUB table. * This is only availabel when encoding is "unicode". */ if (cmap_add) { count += handle_subst_glyphs(cmap, cmap_add, used_chars_copy); } else { /* Else, try gathering information from GSUB tables */ count += otl_gsub_add_ToUnicode(cmap, used_chars_copy, map_base, map_sub, num_glyphs, GIDToCIDMap, sfont); } /* Find Unicode mapping via PostScript glyph names... */ count += add_ToUnicode_via_glyph_name(cmap, used_chars_copy, num_glyphs, GIDToCIDMap, sfont, is_cidfont ? NULL : cffont); if (cffont) cff_close(cffont); /* Finaly, PUA and presentation forms... */ for (gid = 0; gid < num_glyphs; gid++) { uint16_t cid = GIDToCIDMap[gid]; if (is_used_char2(used_chars_copy, cid)) { int32_t ch; unsigned char src[2], dst[4]; unsigned char *p = dst, *endptr = dst + 4; size_t len; ch = map_sub[gid]; if (UC_is_valid(ch)) { src[0] = (cid >> 8) & 0xff; src[1] = cid & 0xff; len = UC_UTF16BE_encode_char(ch, &p, endptr); CMap_add_bfchar(cmap, src, 2, dst, len); used_chars_copy[cid / 8] &= ~(1 << (7 - (cid % 8))); count++; } } } /* Check for missing mapping */ if (dpx_conf.verbose_level > VERBOSE_LEVEL_MIN) { for (gid = 0; gid < num_glyphs; gid++) { uint16_t cid = GIDToCIDMap[gid]; if (is_used_char2(used_chars_copy, cid)) { WARN("Unable to find ToUnicode mapping for glyph CID=%u (GID=%u)", cid, gid); } } } RELEASE(GIDToCIDMap); RELEASE(used_chars_copy); if (count < 1) stream = NULL; else { stream = CMap_create_stream(cmap); } CMap_release(cmap); } RELEASE(map_base); RELEASE(map_sub); return stream; } typedef struct { short platform; short encoding; } cmap_plat_enc_rec; static cmap_plat_enc_rec cmap_plat_encs[] = { { 3, 10 }, { 0, 3 }, { 0, 0 }, { 3, 1 }, { 0, 1 } }; pdf_obj * otf_create_ToUnicode_stream (const char *font_name, uint32_t ttc_index, /* 0 for non-TTC */ const char *basefont, const char *used_chars) { pdf_obj *cmap_ref = NULL; /* returned value */ CMap *cmap_add = NULL; char *cmap_name; FILE *fp = NULL; sfnt *sfont; ULONG offset = 0; tt_cmap *ttcmap; int cmap_id, cmap_add_id; int i; cmap_name = NEW(strlen(basefont)+strlen("-UTF16")+1, char); sprintf(cmap_name, "%s-UTF16", basefont); cmap_id = pdf_findresource("CMap", cmap_name); if (cmap_id >= 0) { RELEASE(cmap_name); cmap_ref = pdf_get_resource_reference(cmap_id); return cmap_ref; } if (dpx_conf.verbose_level > VERBOSE_LEVEL_MIN) { MESG("\n"); MESG("otf_cmap>> Creating ToUnicode CMap for \"%s\"...\n", font_name); } if ((fp = DPXFOPEN(font_name, DPX_RES_TYPE_TTFONT)) || (fp = DPXFOPEN(font_name, DPX_RES_TYPE_OTFONT))) { sfont = sfnt_open(fp); } else if ((fp = DPXFOPEN(font_name, DPX_RES_TYPE_DFONT))) { sfont = dfont_open(fp, ttc_index); } else { RELEASE(cmap_name); return NULL; } if (!sfont) { WARN("Could not open OpenType/TrueType font file \"%s\"", font_name); RELEASE(cmap_name); DPXFCLOSE(fp); return NULL; } switch (sfont->type) { case SFNT_TYPE_DFONT: offset = sfont->offset; break; case SFNT_TYPE_TTC: offset = ttc_read_offset(sfont, ttc_index); if (offset == 0) { WARN("Invalid TTC index for font: %s", font_name); sfnt_close(sfont); DPXFCLOSE(fp); RELEASE(cmap_name); return NULL; } break; default: offset = 0; break; } if (sfnt_read_table_directory(sfont, offset) < 0) { WARN("Could not read OpenType/TrueType table directory: %s", font_name); sfnt_close(sfont); DPXFCLOSE(fp); RELEASE(cmap_name); return NULL; } /* cmap_add is used for storing information on ToUnicode mapping for * unencoded glyphs which can be reached only through GSUB substitution. * This is available only when "unicode" is specified in the encoding * field of fontmap. We remember the inverse mapping via cmap_add in this * case. */ { char *cmap_add_name; size_t len; len = strlen(font_name)+strlen("-UCS32-Add")+32; cmap_add_name = NEW(len, char); snprintf(cmap_add_name, len, "%s:%d-UCS32-Add", font_name, ttc_index); cmap_add_name[len-1] = '\0'; cmap_add_id = CMap_cache_find(cmap_add_name); RELEASE(cmap_add_name); if (cmap_add_id < 0) { cmap_add = NULL; } else { cmap_add = CMap_cache_get(cmap_add_id); } } ttcmap = NULL; for (i = 0; i < sizeof(cmap_plat_encs) / sizeof(cmap_plat_enc_rec); ++i) { ttcmap = tt_cmap_read(sfont, cmap_plat_encs[i].platform, cmap_plat_encs[i].encoding); if (!ttcmap) continue; if (ttcmap->format == 4 || ttcmap->format == 12) { break; } else { tt_cmap_release(ttcmap); ttcmap = NULL; } } if (ttcmap) { pdf_obj *cmap_obj; CMap_set_silent(1); /* many warnings without this... */ cmap_obj = create_ToUnicode_cmap(ttcmap, cmap_name, cmap_add, used_chars, sfont); CMap_set_silent(0); if (cmap_obj) { cmap_id = pdf_defineresource("CMap", cmap_name, cmap_obj, PDF_RES_FLUSH_IMMEDIATE); cmap_ref = pdf_get_resource_reference(cmap_id); } tt_cmap_release(ttcmap); } /* Cleanup */ RELEASE(cmap_name); sfnt_close(sfont); DPXFCLOSE(fp); #ifndef LIBDPX if (!cmap_ref) { WARN("Creating ToUnicode CMap failed for \"%s\"", font_name); } #endif return cmap_ref; } /* Creating input CMaps from OT cmap table */ static void load_cmap4 (struct cmap4 *map, uint16_t *GIDToCIDMap, USHORT num_glyphs, otl_gsub *gsub_vert, otl_gsub *gsub_list, CMap *cmap, int32_t *map_base, int32_t *map_sub) { USHORT c0, c1, gid, cid; USHORT j, d, segCount; USHORT ch; int i; unsigned char buf[4]; segCount = map->segCountX2 / 2; for (i = segCount - 1; i >= 0 ; i--) { c0 = map->startCount[i]; c1 = map->endCount[i]; d = map->idRangeOffset[i] / 2 - (segCount - i); for (j = 0; j <= c1 - c0; j++) { ch = c0 + j; if (map->idRangeOffset[i] == 0) { gid = (ch + map->idDelta[i]) & 0xffff; } else if (c0 == 0xffff && c1 == 0xffff && map->idRangeOffset[i] == 0xffff) { /* this is for protection against some old broken fonts... */ gid = 0; } else { gid = (map->glyphIndexArray[j+d] + map->idDelta[i]) & 0xffff; } if (gid != 0 && gid != 0xffff) { /* Apply GSUB features */ if (gsub_list) otl_gsub_apply_chain(gsub_list, &gid); if (gsub_vert) otl_gsub_apply(gsub_vert, &gid); cid = (gid < num_glyphs) ? GIDToCIDMap[gid] : 0; buf[0] = 0; buf[1] = 0; buf[2] = (ch >> 8) & 0xff; buf[3] = ch & 0xff; CMap_add_cidchar(cmap, buf, 4, cid); /* For ToUnicode creation */ if (map_base && map_sub) { if (is_PUA_or_presentation(ch)) { map_sub[gid] = ch; } else { map_base[gid] = ch; } } } } } return; } static void load_cmap12 (struct cmap12 *map, uint16_t *GIDToCIDMap, USHORT num_glyphs, otl_gsub *gsub_vert, otl_gsub *gsub_list, CMap *cmap, int32_t *map_base, int32_t *map_sub) { ULONG i, ch; USHORT gid, cid; unsigned char buf[4]; for (i = 0; i < map->nGroups; i++) { for (ch = map->groups[i].startCharCode; ch <= map->groups[i].endCharCode; ch++) { int d = ch - map->groups[i].startCharCode; gid = (USHORT) ((map->groups[i].startGlyphID + d) & 0xffff); if (gsub_list) otl_gsub_apply_chain(gsub_list, &gid); if (gsub_vert) otl_gsub_apply(gsub_vert, &gid); cid = (gid < num_glyphs) ? GIDToCIDMap[gid] : 0; buf[0] = (ch >> 24) & 0xff; buf[1] = (ch >> 16) & 0xff; buf[2] = (ch >> 8) & 0xff; buf[3] = ch & 0xff; CMap_add_cidchar(cmap, buf, 4, cid); if (map_base && map_sub) { if (is_PUA_or_presentation(ch)) { map_sub[gid] = ch; } else { map_base[gid] = ch; } } } } return; } int otf_load_Unicode_CMap (const char *map_name, uint32_t ttc_index, /* 0 for non-TTC font */ const char *otl_tags, int wmode) { int cmap_id = -1; char *cmap_name = NULL; sfnt *sfont = NULL; ULONG offset = 0; uint16_t num_glyphs = 0; FILE *fp = NULL; tt_cmap *ttcmap = NULL; CIDSysInfo csi = {NULL, NULL, 0}; uint16_t *GIDToCIDMap = NULL; if (!map_name) return -1; /* First look for cache if it was already loaded */ if (otl_tags) { size_t len; len = strlen(map_name)+strlen("-UCS4-H")+strlen(otl_tags)+32; cmap_name = NEW(len, char); if (wmode) snprintf(cmap_name, len, "%s:%d:%s-UCS4-V", map_name, ttc_index, otl_tags); else snprintf(cmap_name, len, "%s:%d:%s-UCS4-H", map_name, ttc_index, otl_tags); cmap_name[len-1] = '\0'; } else { size_t len; len = strlen(map_name)+strlen("-UCS4-H")+32; cmap_name = NEW(len, char); if (wmode) snprintf(cmap_name, len, "%s:%d-UCS4-V", map_name, ttc_index); else { snprintf(cmap_name, len, "%s:%d-UCS4-H", map_name, ttc_index); } cmap_name[len-1] = '\0'; } cmap_id = CMap_cache_find(cmap_name); if (cmap_id >= 0) { RELEASE(cmap_name); if (dpx_conf.verbose_level > VERBOSE_LEVEL_MIN) MESG("otf_cmap>> Found at cmap_id=%d.\n", cmap_id); return cmap_id; } /* CMap not found */ if (dpx_conf.verbose_level > VERBOSE_LEVEL_MIN) { MESG("\n"); MESG("otf_cmap>> Creating Unicode charmap for font=\"%s\" layout=\"%s\"\n", map_name, (otl_tags ? otl_tags : "none")); } fp = DPXFOPEN(map_name, DPX_RES_TYPE_TTFONT); if (!fp) { fp = DPXFOPEN(map_name, DPX_RES_TYPE_OTFONT); } if (!fp) { fp = DPXFOPEN(map_name, DPX_RES_TYPE_DFONT); if (!fp) { RELEASE(cmap_name); return -1; } sfont = dfont_open(fp, ttc_index); } else { sfont = sfnt_open(fp); } if (!sfont) { WARN("Could not open OpenType/TrueType/dfont font file \"%s\"", map_name); RELEASE(cmap_name); DPXFCLOSE(fp); return -1; } switch (sfont->type) { case SFNT_TYPE_TTC: offset = ttc_read_offset(sfont, ttc_index); if (offset == 0) { WARN("Offset=0 returned for font=%s, TTC_index=%d", map_name, ttc_index); RELEASE(cmap_name); sfnt_close(sfont); DPXFCLOSE(fp); return -1; } break; case SFNT_TYPE_TRUETYPE: case SFNT_TYPE_POSTSCRIPT: offset = 0; break; case SFNT_TYPE_DFONT: offset = sfont->offset; break; default: WARN("Not a OpenType/TrueType/TTC font?: %s", map_name); RELEASE(cmap_name); sfnt_close(sfont); DPXFCLOSE(fp); return -1; break; } if (sfnt_read_table_directory(sfont, offset) < 0) { WARN("Could not read OpenType/TrueType table directory: %s", map_name); RELEASE(cmap_name); sfnt_close(sfont); DPXFCLOSE(fp); return -1; } { struct tt_maxp_table *maxp; maxp = tt_read_maxp_table(sfont); num_glyphs = (card16) maxp->numGlyphs; RELEASE(maxp); } GIDToCIDMap = NEW(num_glyphs, uint16_t); memset(GIDToCIDMap, 0, num_glyphs*sizeof(uint16_t)); if (sfont->type == SFNT_TYPE_POSTSCRIPT) { cff_font *cffont; card16 gid; offset = sfnt_find_table_pos(sfont, "CFF "); /* Possibly "CFF2" table for variable font: not supported. */ if (offset == 0) { WARN("PS OpenType but no \"CFF \" table.. Maybe variable font? (not supported)"); RELEASE(cmap_name); RELEASE(GIDToCIDMap); sfnt_close(sfont); DPXFCLOSE(fp); return -1; } cffont = cff_open(sfont->stream, offset, 0); if (!cffont) { RELEASE(cmap_name); RELEASE(GIDToCIDMap); sfnt_close(sfont); DPXFCLOSE(fp); return -1; } if (!(cffont->flag & FONTTYPE_CIDFONT)) { csi.registry = strdup("Adobe"); csi.ordering = strdup("Identity"); csi.supplement = 0; for (gid = 0; gid < num_glyphs; gid++) { GIDToCIDMap[gid] = gid; } } else { if (!cff_dict_known(cffont->topdict, "ROS")) { csi.registry = strdup("Adobe"); csi.ordering = strdup("Identity"); csi.supplement = 0; } else { card16 reg, ord; reg = (card16) cff_dict_get(cffont->topdict, "ROS", 0); ord = (card16) cff_dict_get(cffont->topdict, "ROS", 1); csi.registry = cff_get_string(cffont, reg); csi.ordering = cff_get_string(cffont, ord); csi.supplement = (int) cff_dict_get(cffont->topdict, "ROS", 2); } cff_read_charsets(cffont); create_GIDToCIDMap(GIDToCIDMap, num_glyphs, cffont); } cff_close(cffont); } else { uint16_t gid; csi.registry = strdup("Adobe"); csi.ordering = strdup("Identity"); csi.supplement = 0; for (gid = 0; gid < num_glyphs; gid++) { GIDToCIDMap[gid] = gid; } } ttcmap = tt_cmap_read(sfont, 3, 10); /* Microsoft UCS4 */ if (!ttcmap) { ttcmap = tt_cmap_read(sfont, 3, 1); /* Microsoft UCS2 */ if (!ttcmap) { ttcmap = tt_cmap_read(sfont, 0, 3); /* Unicode 2.0 or later */ } } if (ttcmap) { CMap *cmap = NULL; int32_t *map_base, *map_sub; otl_gsub *gsub_vert = NULL; otl_gsub *gsub_list = NULL; uint32_t gid; if (wmode == 1) { gsub_vert = otl_gsub_new(); if (otl_gsub_add_feat(gsub_vert, "*", "*", "vrt2", sfont) < 0) { if (otl_gsub_add_feat(gsub_vert, "*", "*", "vert", sfont) < 0) { WARN("GSUB feature vrt2/vert not found."); otl_gsub_release(gsub_vert); gsub_vert = NULL; } else { otl_gsub_select(gsub_vert, "*", "*", "vert"); } } else { otl_gsub_select(gsub_vert, "*", "*", "vrt2"); } } else { gsub_vert = NULL; } if (otl_tags) { gsub_list = otl_gsub_new(); if (otl_gsub_add_feat_list(gsub_list, otl_tags, sfont) < 0) { WARN("Reading GSUB feature table(s) failed for \"%s\"", otl_tags); } else { otl_gsub_set_chain(gsub_list, otl_tags); } } else { gsub_list = NULL; } cmap = CMap_new(); CMap_set_name(cmap, cmap_name); CMap_set_type(cmap, CMAP_TYPE_CODE_TO_CID); CMap_set_wmode(cmap, wmode); CMap_add_codespacerange(cmap, lrange_min, lrange_max, 4); CMap_set_CIDSysInfo(cmap, &csi); map_base = NEW(num_glyphs, int32_t); map_sub = NEW(num_glyphs, int32_t); for (gid = 0; gid < num_glyphs; gid++) { map_base[gid] = -1; map_sub[gid] = -1; } switch (ttcmap->format) { case 12: load_cmap12(ttcmap->map, GIDToCIDMap, num_glyphs, gsub_vert, gsub_list, cmap, map_base, map_sub); break; case 4: load_cmap4(ttcmap->map, GIDToCIDMap, num_glyphs, gsub_vert, gsub_list, cmap, map_base, map_sub); break; } if (gsub_vert) otl_gsub_release(gsub_vert); if (gsub_list) otl_gsub_release(gsub_list); tt_cmap_release(ttcmap); if (otl_tags) { CMap *tounicode = NULL; char *tounicode_name; int tounicode_id; size_t name_len; name_len = strlen(map_name)+strlen("-UCS32-Add")+32; tounicode_name = NEW(name_len, char); snprintf(tounicode_name, name_len, "%s:%d-UCS32-Add", map_name, ttc_index); tounicode_name[name_len-1] = '\0'; tounicode_id = CMap_cache_find(tounicode_name); if (tounicode_id >= 0) tounicode = CMap_cache_get(tounicode_id); else { tounicode = CMap_new(); CMap_set_name (tounicode, tounicode_name); CMap_set_type (tounicode, CMAP_TYPE_TO_UNICODE); CMap_set_wmode(tounicode, 0); CMap_add_codespacerange(tounicode, srange_min, srange_max, 2); CMap_set_CIDSysInfo(tounicode, &CSI_UNICODE); CMap_add_bfchar(tounicode, srange_min, 2, srange_max, 2); tounicode_id = CMap_cache_add(tounicode); } RELEASE(tounicode_name); for (gid = 0; gid < num_glyphs; gid++) { uint16_t cid = GIDToCIDMap[gid]; unsigned char src[2], dst[4]; if (cid > 0) { int32_t ch = UC_is_valid(map_base[gid]) ? map_base[gid] : map_sub[gid]; if (UC_is_valid(ch)) { unsigned char *p = dst; unsigned char *endptr = dst + 4; size_t len; src[0] = (cid >> 8) & 0xff; src[1] = cid & 0xff; len = UC_UTF16BE_encode_char(ch, &p, endptr); if (len > 0) { CMap_add_bfchar(tounicode, src, 2, dst, len); } } } } } cmap_id = CMap_cache_add(cmap); } RELEASE(cmap_name); RELEASE(GIDToCIDMap); if (csi.registry) RELEASE(csi.registry); if (csi.ordering) RELEASE(csi.ordering); sfnt_close(sfont); DPXFCLOSE(fp); return cmap_id; } int otf_try_load_GID_to_CID_map (const char *map_name, uint32_t ttc_index, int wmode) { int cmap_id = -1; sfnt *sfont = NULL; ULONG offset = 0; char *cmap_name = NULL; FILE *fp = NULL; int len; if (!map_name) return -1; /* Check if already loaded */ len = strlen(map_name) + 32; cmap_name = NEW(len, char); snprintf(cmap_name, len, "%s:%d-%1d-GID", map_name, ttc_index, wmode); cmap_name[len-1] = '\0'; cmap_id = CMap_cache_find(cmap_name); if (cmap_id >= 0) { RELEASE(cmap_name); if (dpx_conf.verbose_level > VERBOSE_LEVEL_MIN) MESG("otf_cmap>> GID-to-CID mapping found at cmap_id=%d.\n", cmap_id); return cmap_id; } fp = DPXFOPEN(map_name, DPX_RES_TYPE_TTFONT); if (!fp) { fp = DPXFOPEN(map_name, DPX_RES_TYPE_OTFONT); } if (!fp) { fp = DPXFOPEN(map_name, DPX_RES_TYPE_DFONT); if (!fp) { RELEASE(cmap_name); return -1; } sfont = dfont_open(fp, ttc_index); } else { sfont = sfnt_open(fp); } if (!sfont) { WARN("Could not open OpenType/TrueType/dfont font file \"%s\"", map_name); RELEASE(cmap_name); DPXFCLOSE(fp); return -1; } switch (sfont->type) { case SFNT_TYPE_TTC: offset = ttc_read_offset(sfont, ttc_index); if (offset == 0) { WARN("Invalid TTC index for font \"%s\": %d", map_name, ttc_index); sfnt_close(sfont); DPXFCLOSE(fp); RELEASE(cmap_name); return -1; } break; case SFNT_TYPE_TRUETYPE: case SFNT_TYPE_POSTSCRIPT: offset = 0; break; case SFNT_TYPE_DFONT: offset = sfont->offset; break; default: WARN("Not a OpenType/TrueType/TTC font?: %s", map_name); sfnt_close(sfont); DPXFCLOSE(fp); RELEASE(cmap_name); return -1; } if (sfnt_read_table_directory(sfont, offset) < 0) { WARN("Could not read OpenType/TrueType table directory: %s", map_name); sfnt_close(sfont); DPXFCLOSE(fp); RELEASE(cmap_name); return -1; } if (sfont->type != SFNT_TYPE_POSTSCRIPT) { RELEASE(cmap_name); sfnt_close(sfont); DPXFCLOSE(fp); return -1; } /* Read GID-to-CID mapping if CFF OpenType is found. */ if (sfont->type == SFNT_TYPE_POSTSCRIPT) { cff_font *cffont; struct tt_maxp_table *maxp; const unsigned char csrange[4] = {0x00, 0x00, 0xff, 0xff}; uint16_t num_glyphs = 0; maxp = tt_read_maxp_table(sfont); num_glyphs = (card16) maxp->numGlyphs; RELEASE(maxp); offset = sfnt_find_table_pos(sfont, "CFF "); if (offset == 0) { WARN("PS OpenType but no \"CFF \" table.. Maybe variable font? (not supported)"); RELEASE(cmap_name); sfnt_close(sfont); DPXFCLOSE(fp); return -1; } cffont = cff_open(sfont->stream, offset, 0); if (cffont && cffont->flag & FONTTYPE_CIDFONT) { CMap *cmap; uint16_t gid; uint16_t *GIDToCIDMap = NULL; CIDSysInfo csi = {NULL, NULL, 0}; if (!cff_dict_known(cffont->topdict, "ROS")) { csi.registry = strdup("Adobe"); csi.ordering = strdup("Identity"); csi.supplement = 0; } else { card16 reg, ord; reg = (card16) cff_dict_get(cffont->topdict, "ROS", 0); ord = (card16) cff_dict_get(cffont->topdict, "ROS", 1); csi.registry = cff_get_string(cffont, reg); csi.ordering = cff_get_string(cffont, ord); csi.supplement = (int) cff_dict_get(cffont->topdict, "ROS", 2); } cff_read_charsets(cffont); GIDToCIDMap = NEW(num_glyphs, uint16_t); memset(GIDToCIDMap, 0, num_glyphs*sizeof(uint16_t)); create_GIDToCIDMap(GIDToCIDMap, num_glyphs, cffont); cmap = CMap_new(); CMap_set_name (cmap, cmap_name); CMap_set_type (cmap, CMAP_TYPE_CODE_TO_CID); CMap_set_wmode(cmap, wmode); CMap_add_codespacerange(cmap, &csrange[0], &csrange[2], 2); CMap_set_CIDSysInfo(cmap, &csi); for (gid = 0; gid < num_glyphs; gid++) { unsigned char src[2], dst[2]; src[0] = (gid >> 8) & 0xff; src[1] = gid & 0xff; dst[0] = (GIDToCIDMap[gid] >> 8) & 0xff; dst[1] = GIDToCIDMap[gid] & 0xff; CMap_add_bfchar(cmap, src, 2, dst, 2); } cmap_id = CMap_cache_add(cmap); if (dpx_conf.verbose_level > VERBOSE_LEVEL_MIN) { MESG("\n"); MESG("otf_cmap>> Creating GID-to-CID mapping for font=\"%s\"\n", map_name); } RELEASE(GIDToCIDMap); if (csi.registry) RELEASE(csi.registry); if (csi.ordering) RELEASE(csi.ordering); } if (cffont) cff_close(cffont); } RELEASE(cmap_name); sfnt_close(sfont); DPXFCLOSE(fp); return cmap_id; }