File Coverage

src/pdfmake_tounicode.c
Criterion Covered Total %
statement 0 123 0.0
branch 0 98 0.0
condition n/a
subroutine n/a
pod n/a
total 0 221 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_tounicode.c - ToUnicode CMap generation
3             *
4             * Generates a ToUnicode CMap stream that maps glyph codes to Unicode
5             * codepoints, enabling text extraction and copy/paste from PDFs.
6             *
7             * Reference: PDF spec ยง9.10.3, Adobe Technical Note #5014 (CMap)
8             */
9              
10             #include "pdfmake_font.h"
11             #include "pdfmake_buf.h"
12             #include
13             #include
14             #include
15              
16             /*============================================================================
17             * ToUnicode CMap structure
18             *
19             * The CMap maps character codes (glyph IDs for CIDFont) to Unicode.
20             * We use bfchar entries for individual mappings and bfrange for
21             * contiguous ranges.
22             *==========================================================================*/
23              
24             typedef struct {
25             uint16_t glyph_id;
26             uint32_t unicode;
27             } glyph_unicode_t;
28              
29             /*============================================================================
30             * Build glyph->Unicode mapping from reverse cmap lookup
31             *==========================================================================*/
32              
33 0           static int compare_gu(const void *a, const void *b) {
34 0           const glyph_unicode_t *ga = a;
35 0           const glyph_unicode_t *gb = b;
36 0 0         if (ga->glyph_id < gb->glyph_id) return -1;
37 0 0         if (ga->glyph_id > gb->glyph_id) return 1;
38 0           return 0;
39             }
40              
41             /*============================================================================
42             * Generate ToUnicode CMap for font
43             *==========================================================================*/
44              
45 0           pdfmake_err_t pdfmake_tounicode_generate(const pdfmake_font_t *font,
46             pdfmake_buf_t *out_buf) {
47             pdfmake_ttf_t *ttf;
48             size_t used_count;
49             int i;
50             size_t byte;
51             uint8_t bit;
52             glyph_unicode_t *mappings;
53             size_t map_count;
54             uint32_t cp;
55             uint16_t gid;
56             int exists;
57             size_t j;
58             size_t mi;
59             size_t range_start;
60             size_t range_len;
61             size_t next;
62             uint16_t gid_start;
63             uint16_t gid_end;
64             uint32_t unicode_start;
65             size_t batch_count;
66             size_t batch_start;
67             size_t possible_range;
68             uint32_t unicode;
69              
70 0 0         if (!font || !out_buf) return PDFMAKE_EINVAL;
    0          
71            
72             /* For Standard 14 fonts, use WinAnsi ToUnicode */
73 0 0         if (font->type == PDFMAKE_FONT_TYPE1) {
74             /* WinAnsi to Unicode mapping (codes 128-159 differ from Latin-1) */
75             static const uint16_t winansi_unicode[] = {
76             /* 128-143 */
77             0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
78             0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
79             /* 144-159 */
80             0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
81             0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
82             };
83            
84             /* CMap header */
85 0           pdfmake_buf_appendf(out_buf,
86             "/CIDInit /ProcSet findresource begin\n"
87             "12 dict begin\n"
88             "begincmap\n"
89             "/CIDSystemInfo <<\n"
90             " /Registry (Adobe)\n"
91             " /Ordering (UCS)\n"
92             " /Supplement 0\n"
93             ">> def\n"
94             "/CMapName /Adobe-Identity-UCS def\n"
95             "/CMapType 2 def\n"
96             "1 begincodespacerange\n"
97             "<00> \n"
98             "endcodespacerange\n"
99             );
100            
101             /* Range 32-127: direct ASCII mapping */
102 0           pdfmake_buf_appendf(out_buf,
103             "2 beginbfrange\n"
104             "<20> <7E> <0020>\n"
105             " <00A0>\n"
106             "endbfrange\n"
107             );
108            
109             /* Individual mappings for 128-159 (WinAnsi special) */
110 0           pdfmake_buf_appendf(out_buf, "32 beginbfchar\n");
111            
112 0 0         for (i = 0; i < 32; i++) {
113 0           pdfmake_buf_appendf(out_buf, "<%02X> <%04X>\n",
114 0           128 + i, winansi_unicode[i]);
115             }
116            
117 0           pdfmake_buf_appendf(out_buf, "endbfchar\n");
118            
119             /* CMap trailer */
120 0           pdfmake_buf_appendf(out_buf,
121             "endcmap\n"
122             "CMapName currentdict /CMap defineresource pop\n"
123             "end\n"
124             "end\n"
125             );
126            
127 0           return PDFMAKE_OK;
128             }
129            
130             /* For TrueType fonts, build from used glyphs */
131 0 0         if (font->type != PDFMAKE_FONT_TRUETYPE || !font->ttf) {
    0          
132 0           return PDFMAKE_EINVAL;
133             }
134            
135 0           ttf = font->ttf;
136            
137             /* Count used glyphs */
138 0           used_count = 0;
139 0 0         for (i = 1; i < ttf->num_glyphs; i++) {
140 0           byte = i / 8;
141 0           bit = 1 << (i % 8);
142 0 0         if (ttf->used_glyphs && (ttf->used_glyphs[byte] & bit)) {
    0          
143 0           used_count++;
144             }
145             }
146            
147 0 0         if (used_count == 0) {
148 0           return PDFMAKE_OK; /* Nothing to map */
149             }
150            
151             /* Build glyph->unicode mapping by scanning used codepoints */
152             /* We need to reverse the cmap lookup - scan common Unicode ranges */
153 0           mappings = calloc(used_count, sizeof(glyph_unicode_t));
154 0 0         if (!mappings) return PDFMAKE_ENOMEM;
155            
156 0           map_count = 0;
157            
158             /* Scan BMP range */
159 0 0         for (cp = 0x20; cp < 0x10000 && map_count < used_count; cp++) {
    0          
160 0           gid = pdfmake_ttf_cmap_lookup(ttf, cp);
161 0 0         if (gid > 0) {
162 0           byte = gid / 8;
163 0           bit = 1 << (gid % 8);
164 0 0         if (ttf->used_glyphs && (ttf->used_glyphs[byte] & bit)) {
    0          
165             /* Check if we already have this glyph */
166 0           exists = 0;
167 0 0         for (j = 0; j < map_count; j++) {
168 0 0         if (mappings[j].glyph_id == gid) {
169 0           exists = 1;
170 0           break;
171             }
172             }
173 0 0         if (!exists) {
174 0           mappings[map_count].glyph_id = gid;
175 0           mappings[map_count].unicode = cp;
176 0           map_count++;
177             }
178             }
179             }
180             }
181            
182 0 0         if (map_count == 0) {
183 0           free(mappings);
184 0           return PDFMAKE_OK;
185             }
186            
187             /* Sort by glyph ID */
188 0           qsort(mappings, map_count, sizeof(glyph_unicode_t), compare_gu);
189            
190             /* CMap header */
191 0           pdfmake_buf_appendf(out_buf,
192             "/CIDInit /ProcSet findresource begin\n"
193             "12 dict begin\n"
194             "begincmap\n"
195             "/CIDSystemInfo <<\n"
196             " /Registry (Adobe)\n"
197             " /Ordering (UCS)\n"
198             " /Supplement 0\n"
199             ">> def\n"
200             "/CMapName /Adobe-Identity-UCS def\n"
201             "/CMapType 2 def\n"
202             "1 begincodespacerange\n"
203             "<0000> \n"
204             "endcodespacerange\n"
205             );
206            
207             /* Emit mappings */
208 0           mi = 0;
209 0 0         while (mi < map_count) {
210             /* Try to find a range */
211 0           range_start = mi;
212 0           range_len = 1;
213            
214 0 0         while (range_start + range_len < map_count) {
215 0           next = range_start + range_len;
216 0 0         if (mappings[next].glyph_id == mappings[range_start].glyph_id + range_len &&
217 0 0         mappings[next].unicode == mappings[range_start].unicode + range_len) {
218 0           range_len++;
219             } else {
220             break;
221             }
222             }
223            
224 0 0         if (range_len >= 3) {
225             /* Emit as range */
226 0           pdfmake_buf_appendf(out_buf, "1 beginbfrange\n");
227            
228 0           gid_start = mappings[range_start].glyph_id;
229 0           gid_end = gid_start + range_len - 1;
230 0           unicode_start = mappings[range_start].unicode;
231            
232 0           pdfmake_buf_appendf(out_buf, "<%04X> <%04X> <%04X>\n",
233             gid_start, gid_end, (unsigned)unicode_start);
234            
235 0           pdfmake_buf_appendf(out_buf, "endbfrange\n");
236 0           mi += range_len;
237             } else {
238             /* Emit individual characters */
239 0           batch_count = 0;
240 0           batch_start = mi;
241            
242 0 0         while (mi < map_count && batch_count < 100) {
    0          
243 0           possible_range = 1;
244 0 0         while (mi + possible_range < map_count) {
245 0           next = mi + possible_range;
246 0 0         if (mappings[next].glyph_id == mappings[mi].glyph_id + possible_range &&
247 0 0         mappings[next].unicode == mappings[mi].unicode + possible_range) {
248 0           possible_range++;
249             } else {
250             break;
251             }
252             }
253            
254 0 0         if (possible_range >= 3) break;
255            
256 0           batch_count++;
257 0           mi++;
258             }
259            
260 0 0         if (batch_count > 0) {
261 0           pdfmake_buf_appendf(out_buf, "%zu beginbfchar\n", batch_count);
262            
263 0 0         for (j = 0; j < batch_count; j++) {
264 0           gid = mappings[batch_start + j].glyph_id;
265 0           unicode = mappings[batch_start + j].unicode;
266 0           pdfmake_buf_appendf(out_buf, "<%04X> <%04X>\n", gid, (unsigned)unicode);
267             }
268            
269 0           pdfmake_buf_appendf(out_buf, "endbfchar\n");
270             }
271             }
272             }
273            
274             /* CMap trailer */
275 0           pdfmake_buf_appendf(out_buf,
276             "endcmap\n"
277             "CMapName currentdict /CMap defineresource pop\n"
278             "end\n"
279             "end\n"
280             );
281            
282 0           free(mappings);
283 0           return PDFMAKE_OK;
284             }
285              
286             /*============================================================================
287             * Generate CID width array (W entry for CIDFont)
288             *
289             * Format: [ c [w1 w2 ...] c [w1 w2 ...] ... ]
290             * or: [ c_first c_last w ]
291             *==========================================================================*/
292              
293 0           pdfmake_err_t pdfmake_cid_widths(const pdfmake_font_t *font,
294             pdfmake_buf_t *out_buf) {
295             pdfmake_ttf_t *ttf;
296             size_t used_count;
297             int i;
298             size_t byte;
299             uint8_t bit;
300             int in_group;
301             uint16_t advance;
302              
303 0 0         if (!font || !out_buf) return PDFMAKE_EINVAL;
    0          
304            
305 0 0         if (font->type != PDFMAKE_FONT_TRUETYPE || !font->ttf) {
    0          
306 0           return PDFMAKE_EINVAL;
307             }
308            
309 0           ttf = font->ttf;
310            
311             /* Collect used glyphs in order */
312 0           used_count = 0;
313 0 0         for (i = 0; i < ttf->num_glyphs; i++) {
314 0           byte = i / 8;
315 0           bit = 1 << (i % 8);
316 0 0         if (ttf->used_glyphs && (ttf->used_glyphs[byte] & bit)) {
    0          
317 0           used_count++;
318             }
319             }
320            
321 0 0         if (used_count == 0) {
322 0           pdfmake_buf_appendf(out_buf, "[]");
323 0           return PDFMAKE_OK;
324             }
325            
326 0           pdfmake_buf_appendf(out_buf, "[");
327            
328             /* Group consecutive glyphs */
329 0           in_group = 0;
330            
331 0 0         for (i = 0; i < ttf->num_glyphs; i++) {
332 0           byte = i / 8;
333 0           bit = 1 << (i % 8);
334            
335 0 0         if (ttf->used_glyphs && (ttf->used_glyphs[byte] & bit)) {
    0          
336 0 0         if (!in_group) {
337 0           in_group = 1;
338 0           pdfmake_buf_appendf(out_buf, " %d [", i);
339             }
340            
341 0           advance = pdfmake_ttf_glyph_advance(ttf, i);
342 0           pdfmake_buf_appendf(out_buf, " %d", advance);
343 0 0         } else if (in_group) {
344 0           pdfmake_buf_appendf(out_buf, " ]");
345 0           in_group = 0;
346             }
347             }
348            
349 0 0         if (in_group) {
350 0           pdfmake_buf_appendf(out_buf, " ]");
351             }
352            
353 0           pdfmake_buf_appendf(out_buf, " ]");
354            
355 0           return PDFMAKE_OK;
356             }