File Coverage

src/pdfmake_ttf_subset.c
Criterion Covered Total %
statement 0 256 0.0
branch 0 112 0.0
condition n/a
subroutine n/a
pod n/a
total 0 368 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_ttf_subset.c - TrueType font subsetter
3             *
4             * Creates a minimal TrueType font containing only the glyphs used.
5             * Required tables: head, hhea, maxp, loca, glyf, hmtx, cmap, post
6             *
7             * Reference: OpenType/TrueType specification
8             */
9              
10             #include "pdfmake_font.h"
11             #include "pdfmake_internal.h"
12             #include
13             #include
14              
15             /*============================================================================
16             * Table tag helpers
17             *==========================================================================*/
18              
19             #define TAG(a,b,c,d) (((uint32_t)(a)<<24)|((uint32_t)(b)<<16)|((uint32_t)(c)<<8)|(d))
20              
21             /*============================================================================
22             * Calculate TTF table checksum
23             *==========================================================================*/
24              
25 0           static uint32_t ttf_checksum(const uint8_t *data, size_t len) {
26 0           uint32_t sum = 0;
27 0           size_t nwords = (len + 3) / 4;
28             size_t i;
29            
30 0 0         for (i = 0; i < nwords; i++) {
31 0           uint32_t word = 0;
32 0           size_t offset = i * 4;
33            
34 0 0         if (offset < len) word |= (uint32_t)data[offset] << 24;
35 0 0         if (offset + 1 < len) word |= (uint32_t)data[offset + 1] << 16;
36 0 0         if (offset + 2 < len) word |= (uint32_t)data[offset + 2] << 8;
37 0 0         if (offset + 3 < len) word |= (uint32_t)data[offset + 3];
38            
39 0           sum += word;
40             }
41            
42 0           return sum;
43             }
44              
45             /*============================================================================
46             * Build glyph remapping table
47             *==========================================================================*/
48              
49             typedef struct {
50             uint16_t *old_to_new; /* old_glyph_id -> new_glyph_id */
51             uint16_t *new_to_old; /* new_glyph_id -> old_glyph_id */
52             uint16_t new_count;
53             } glyph_map_t;
54              
55 0           static glyph_map_t *build_glyph_map(pdfmake_ttf_t *ttf) {
56 0           glyph_map_t *map = calloc(1, sizeof(glyph_map_t));
57 0 0         if (!map) return NULL;
58            
59 0           map->old_to_new = calloc(ttf->num_glyphs, sizeof(uint16_t));
60 0           map->new_to_old = calloc(ttf->num_glyphs, sizeof(uint16_t));
61 0 0         if (!map->old_to_new || !map->new_to_old) {
    0          
62 0           free(map->old_to_new);
63 0           free(map->new_to_old);
64 0           free(map);
65 0           return NULL;
66             }
67            
68             /* Always include glyph 0 (.notdef) */
69             {
70             int i;
71 0           map->old_to_new[0] = 0;
72 0           map->new_to_old[0] = 0;
73 0           map->new_count = 1;
74            
75             /* Add used glyphs in order */
76 0 0         for (i = 1; i < ttf->num_glyphs; i++) {
77 0           size_t byte = i / 8;
78 0           uint8_t bit = 1 << (i % 8);
79            
80 0 0         if (ttf->used_glyphs[byte] & bit) {
81 0           map->old_to_new[i] = map->new_count;
82 0           map->new_to_old[map->new_count] = i;
83 0           map->new_count++;
84             }
85             }
86             }
87            
88 0           return map;
89             }
90              
91 0           static void free_glyph_map(glyph_map_t *map) {
92 0 0         if (!map) return;
93 0           free(map->old_to_new);
94 0           free(map->new_to_old);
95 0           free(map);
96             }
97              
98             /*============================================================================
99             * Get glyph data location from loca table
100             *==========================================================================*/
101              
102 0           static int get_glyph_location(pdfmake_ttf_t *ttf, uint16_t glyph_id,
103             uint32_t *offset, uint32_t *length) {
104             const uint8_t *loca;
105             uint32_t off1, off2;
106            
107 0 0         if (glyph_id >= ttf->num_glyphs) return 0;
108            
109 0           loca = ttf->data + ttf->loca.offset;
110            
111 0 0         if (ttf->index_to_loc_format == 0) {
112             /* Short format: offsets are words, multiply by 2 */
113 0           off1 = pdfmake_read_be16(loca + glyph_id * 2) * 2;
114 0           off2 = pdfmake_read_be16(loca + (glyph_id + 1) * 2) * 2;
115             } else {
116             /* Long format: offsets are dwords */
117 0           off1 = pdfmake_read_be32(loca + glyph_id * 4);
118 0           off2 = pdfmake_read_be32(loca + (glyph_id + 1) * 4);
119             }
120            
121 0           *offset = off1;
122 0           *length = off2 - off1;
123 0           return 1;
124             }
125              
126             /*============================================================================
127             * Build subset glyf table
128             *==========================================================================*/
129              
130             typedef struct {
131             uint8_t *data;
132             size_t len;
133             uint32_t *offsets; /* num_glyphs + 1 offsets for loca */
134             } glyf_result_t;
135              
136 0           static glyf_result_t *build_subset_glyf(pdfmake_ttf_t *ttf, glyph_map_t *map) {
137 0           glyf_result_t *result = calloc(1, sizeof(glyf_result_t));
138 0 0         if (!result) return NULL;
139            
140 0           result->offsets = calloc(map->new_count + 1, sizeof(uint32_t));
141 0 0         if (!result->offsets) {
142 0           free(result);
143 0           return NULL;
144             }
145            
146             /* First pass: calculate total size */
147             {
148 0           size_t total = 0;
149             uint16_t i;
150 0 0         for (i = 0; i < map->new_count; i++) {
151 0           uint16_t old_id = map->new_to_old[i];
152             uint32_t off, len;
153 0 0         if (get_glyph_location(ttf, old_id, &off, &len)) {
154             /* Align to 2 bytes (short loca format) or 4 bytes (long) */
155 0           total += len;
156 0 0         if (len % 2) total++; /* Pad to even */
157             }
158             }
159            
160 0 0         result->data = calloc(1, total > 0 ? total : 1);
161             }
162 0 0         if (!result->data) {
163 0           free(result->offsets);
164 0           free(result);
165 0           return NULL;
166             }
167            
168             /* Second pass: copy glyph data */
169             {
170 0           size_t pos = 0;
171             uint16_t i;
172 0 0         for (i = 0; i < map->new_count; i++) {
173             uint16_t old_id;
174             uint32_t off, len;
175 0           result->offsets[i] = pos;
176            
177 0           old_id = map->new_to_old[i];
178 0 0         if (get_glyph_location(ttf, old_id, &off, &len) && len > 0) {
    0          
179 0           memcpy(result->data + pos, ttf->data + ttf->glyf.offset + off, len);
180 0           pos += len;
181 0 0         if (len % 2) pos++; /* Pad to even */
182             }
183             }
184 0           result->offsets[map->new_count] = pos;
185 0           result->len = pos;
186             }
187            
188 0           return result;
189             }
190              
191 0           static void free_glyf_result(glyf_result_t *r) {
192 0 0         if (!r) return;
193 0           free(r->data);
194 0           free(r->offsets);
195 0           free(r);
196             }
197              
198             /*============================================================================
199             * Build subset loca table
200             *==========================================================================*/
201              
202 0           static uint8_t *build_subset_loca(glyph_map_t *map, glyf_result_t *glyf,
203             int short_format, size_t *out_len) {
204 0           size_t count = map->new_count + 1;
205             size_t len;
206             uint8_t *data;
207            
208 0 0         if (short_format) {
209             size_t i;
210 0           len = count * 2;
211 0           data = calloc(1, len);
212 0 0         if (!data) return NULL;
213            
214 0 0         for (i = 0; i < count; i++) {
215 0           pdfmake_write_be16(data + i * 2, glyf->offsets[i] / 2);
216             }
217             } else {
218             size_t i;
219 0           len = count * 4;
220 0           data = calloc(1, len);
221 0 0         if (!data) return NULL;
222            
223 0 0         for (i = 0; i < count; i++) {
224 0           pdfmake_write_be32(data + i * 4, glyf->offsets[i]);
225             }
226             }
227            
228 0           *out_len = len;
229 0           return data;
230             }
231              
232             /*============================================================================
233             * Build subset hmtx table
234             *==========================================================================*/
235              
236 0           static uint8_t *build_subset_hmtx(pdfmake_ttf_t *ttf, glyph_map_t *map,
237             size_t *out_len) {
238             /* Each glyph gets full metric (advance + lsb) */
239 0           size_t len = map->new_count * 4;
240 0           uint8_t *data = calloc(1, len);
241             const uint8_t *hmtx;
242             uint16_t i;
243 0 0         if (!data) return NULL;
244            
245 0           hmtx = ttf->data + ttf->hmtx.offset;
246            
247 0 0         for (i = 0; i < map->new_count; i++) {
248 0           uint16_t old_id = map->new_to_old[i];
249             uint16_t advance, lsb;
250            
251 0 0         if (old_id < ttf->num_h_metrics) {
252 0           advance = pdfmake_read_be16(hmtx + old_id * 4);
253 0           lsb = pdfmake_read_be16(hmtx + old_id * 4 + 2);
254             } else {
255             /* Last advance, variable lsb */
256             size_t lsb_offset;
257 0           advance = pdfmake_read_be16(hmtx + (ttf->num_h_metrics - 1) * 4);
258 0           lsb_offset = ttf->num_h_metrics * 4 +
259 0           (old_id - ttf->num_h_metrics) * 2;
260 0 0         if (ttf->hmtx.offset + lsb_offset + 2 <= ttf->data_len) {
261 0           lsb = pdfmake_read_be16(hmtx + lsb_offset);
262             } else {
263 0           lsb = 0;
264             }
265             }
266            
267 0           pdfmake_write_be16(data + i * 4, advance);
268 0           pdfmake_write_be16(data + i * 4 + 2, lsb);
269             }
270            
271 0           *out_len = len;
272 0           return data;
273             }
274              
275             /*============================================================================
276             * Build minimal cmap table (format 4 or format 12)
277             *
278             * For PDF embedding, we typically use Identity-H/Identity-V with CIDFont,
279             * so cmap maps GID to GID directly. But for simple TrueType, we need
280             * a proper Unicode cmap.
281             *==========================================================================*/
282              
283 0           static uint8_t *build_subset_cmap_format4(glyph_map_t *map, size_t *out_len) {
284             /* Build a simple format 4 cmap for the subset */
285             /* For PDF embedding with /Identity-H, glyphs are accessed by GID directly */
286             /* We create a minimal cmap that maps GID -> GID */
287            
288             /* Simplified: single segment 0 to num_glyphs-1 */
289 0           uint16_t seg_count = 2; /* One real segment + terminator */
290 0           size_t len = 4 + 8 + 14 + seg_count * 8; /* header + encoding record + format 4 */
291            
292 0           uint8_t *data = calloc(1, len);
293 0 0         if (!data) return NULL;
294            
295             /* cmap header */
296 0           pdfmake_write_be16(data, 0); /* version */
297 0           pdfmake_write_be16(data + 2, 1); /* numTables */
298            
299             /* Encoding record: platform 3 (Windows), encoding 1 (Unicode BMP) */
300 0           pdfmake_write_be16(data + 4, 3); /* platformID */
301 0           pdfmake_write_be16(data + 6, 1); /* encodingID */
302 0           pdfmake_write_be32(data + 8, 12); /* offset to subtable */
303            
304             /* Format 4 subtable */
305             {
306 0           uint8_t *fmt = data + 12;
307 0           uint16_t fmt_len = 14 + seg_count * 8;
308             /* Segment arrays */
309             uint8_t *endCode;
310             uint8_t *reservedPad;
311             uint8_t *startCode;
312             uint8_t *idDelta;
313             uint8_t *idRangeOffset;
314            
315 0           pdfmake_write_be16(fmt, 4); /* format */
316 0           pdfmake_write_be16(fmt + 2, fmt_len); /* length */
317 0           pdfmake_write_be16(fmt + 4, 0); /* language */
318 0           pdfmake_write_be16(fmt + 6, seg_count * 2); /* segCountX2 */
319 0           pdfmake_write_be16(fmt + 8, 2); /* searchRange */
320 0           pdfmake_write_be16(fmt + 10, 0); /* entrySelector */
321 0           pdfmake_write_be16(fmt + 12, 2); /* rangeShift */
322            
323 0           endCode = fmt + 14;
324 0           reservedPad = endCode + seg_count * 2;
325 0           startCode = reservedPad + 2;
326 0           idDelta = startCode + seg_count * 2;
327 0           idRangeOffset = idDelta + seg_count * 2;
328            
329             /* Segment 0: 0 to num_glyphs-1 -> direct mapping (delta=0) */
330 0           pdfmake_write_be16(endCode, map->new_count - 1);
331 0           pdfmake_write_be16(startCode, 0);
332 0           pdfmake_write_be16(idDelta, 0);
333 0           pdfmake_write_be16(idRangeOffset, 0);
334            
335             /* Terminator segment (required): 0xFFFF */
336 0           pdfmake_write_be16(endCode + 2, 0xFFFF);
337 0           pdfmake_write_be16(reservedPad, 0);
338 0           pdfmake_write_be16(startCode + 2, 0xFFFF);
339 0           pdfmake_write_be16(idDelta + 2, 1);
340 0           pdfmake_write_be16(idRangeOffset + 2, 0);
341             }
342            
343 0           *out_len = len;
344 0           return data;
345             }
346              
347             /*============================================================================
348             * Copy and adjust head table
349             *==========================================================================*/
350              
351 0           static uint8_t *build_subset_head(pdfmake_ttf_t *ttf, int short_loca,
352             size_t *out_len) {
353             uint8_t *data;
354 0 0         if (ttf->head.length < 54) return NULL;
355            
356 0           data = malloc(ttf->head.length);
357 0 0         if (!data) return NULL;
358            
359 0           memcpy(data, ttf->data + ttf->head.offset, ttf->head.length);
360            
361             /* Update indexToLocFormat */
362 0           pdfmake_write_be16(data + 50, short_loca ? 0 : 1);
363            
364             /* Clear checkSumAdjustment (will be updated later) */
365 0           pdfmake_write_be32(data + 8, 0);
366            
367 0           *out_len = ttf->head.length;
368 0           return data;
369             }
370              
371             /*============================================================================
372             * Copy and adjust maxp table
373             *==========================================================================*/
374              
375 0           static uint8_t *build_subset_maxp(pdfmake_ttf_t *ttf, glyph_map_t *map,
376             size_t *out_len) {
377 0           uint8_t *data = malloc(ttf->maxp.length);
378 0 0         if (!data) return NULL;
379            
380 0           memcpy(data, ttf->data + ttf->maxp.offset, ttf->maxp.length);
381            
382             /* Update numGlyphs */
383 0           pdfmake_write_be16(data + 4, map->new_count);
384            
385 0           *out_len = ttf->maxp.length;
386 0           return data;
387             }
388              
389             /*============================================================================
390             * Copy and adjust hhea table
391             *==========================================================================*/
392              
393 0           static uint8_t *build_subset_hhea(pdfmake_ttf_t *ttf, glyph_map_t *map,
394             size_t *out_len) {
395 0           uint8_t *data = malloc(ttf->hhea.length);
396 0 0         if (!data) return NULL;
397            
398 0           memcpy(data, ttf->data + ttf->hhea.offset, ttf->hhea.length);
399            
400             /* Update numberOfHMetrics - all glyphs have full metrics */
401 0           pdfmake_write_be16(data + 34, map->new_count);
402            
403 0           *out_len = ttf->hhea.length;
404 0           return data;
405             }
406              
407             /*============================================================================
408             * Build minimal post table
409             *==========================================================================*/
410              
411 0           static uint8_t *build_subset_post(size_t *out_len) {
412             /* Create minimal version 3.0 post table (no glyph names) */
413 0           size_t len = 32;
414 0           uint8_t *data = calloc(1, len);
415 0 0         if (!data) return NULL;
416            
417 0           pdfmake_write_be32(data, 0x00030000); /* version 3.0 */
418             /* italicAngle, underlinePosition, etc. default to 0 */
419            
420 0           *out_len = len;
421 0           return data;
422             }
423              
424             /*============================================================================
425             * Assemble final subset TTF
426             *==========================================================================*/
427              
428 0           pdfmake_err_t pdfmake_ttf_subset(const pdfmake_ttf_t *ttf, pdfmake_buf_t *out_buf) {
429             /* Cast away const for internal processing - we don't modify the source */
430             pdfmake_ttf_t *mutable_ttf;
431             glyph_map_t *map;
432             glyf_result_t *glyf;
433             int short_loca;
434             size_t loca_len, hmtx_len, cmap_len, head_len, maxp_len, hhea_len, post_len;
435             uint8_t *loca, *hmtx, *cmap, *head, *maxp, *hhea, *post;
436 0           const int num_tables = 8; /* head, hhea, maxp, loca, glyf, hmtx, cmap, post */
437             size_t header_size;
438             /* Align each table to 4 bytes */
439             #define ALIGN4(x) (((x) + 3) & ~3)
440             size_t tables_size;
441             size_t total_size;
442             uint8_t *result;
443             int power, selector;
444             uint8_t *dir;
445             size_t offset;
446             int i;
447             uint32_t file_checksum;
448             uint32_t head_offset;
449             struct table_entry {
450             uint32_t tag;
451             uint8_t *data;
452             size_t len;
453             } tables[8];
454            
455 0 0         if (!ttf || !out_buf) return PDFMAKE_EINVAL;
    0          
456            
457 0           mutable_ttf = (pdfmake_ttf_t *)ttf;
458            
459 0           map = build_glyph_map(mutable_ttf);
460 0 0         if (!map) return PDFMAKE_ENOMEM;
461            
462             /* Build subset tables */
463 0           glyf = build_subset_glyf(mutable_ttf, map);
464 0 0         if (!glyf) { free_glyph_map(map); return PDFMAKE_ENOMEM; }
465            
466             /* Determine if we can use short loca format */
467 0           short_loca = (glyf->len / 2) <= 0xFFFF;
468            
469 0           loca = build_subset_loca(map, glyf, short_loca, &loca_len);
470 0           hmtx = build_subset_hmtx(mutable_ttf, map, &hmtx_len);
471 0           cmap = build_subset_cmap_format4(map, &cmap_len);
472 0           head = build_subset_head(mutable_ttf, short_loca, &head_len);
473 0           maxp = build_subset_maxp(mutable_ttf, map, &maxp_len);
474 0           hhea = build_subset_hhea(mutable_ttf, map, &hhea_len);
475 0           post = build_subset_post(&post_len);
476            
477 0 0         if (!loca || !hmtx || !cmap || !head || !maxp || !hhea || !post) {
    0          
    0          
    0          
    0          
    0          
    0          
478 0           free(loca); free(hmtx); free(cmap); free(head);
479 0           free(maxp); free(hhea); free(post);
480 0           free_glyf_result(glyf);
481 0           free_glyph_map(map);
482 0           return PDFMAKE_ENOMEM;
483             }
484            
485             /* Calculate file size */
486 0           header_size = 12 + num_tables * 16;
487            
488 0           tables_size = ALIGN4(head_len) + ALIGN4(hhea_len) + ALIGN4(maxp_len) +
489 0           ALIGN4(cmap_len) + ALIGN4(hmtx_len) + ALIGN4(loca_len) +
490 0           ALIGN4(glyf->len) + ALIGN4(post_len);
491            
492 0           total_size = header_size + tables_size;
493            
494 0           result = calloc(1, total_size);
495 0 0         if (!result) {
496 0           free(loca); free(hmtx); free(cmap); free(head);
497 0           free(maxp); free(hhea); free(post);
498 0           free_glyf_result(glyf);
499 0           free_glyph_map(map);
500 0           return PDFMAKE_ENOMEM;
501             }
502            
503             /* Write offset table header */
504 0           pdfmake_write_be32(result, 0x00010000); /* sfntVersion */
505 0           pdfmake_write_be16(result + 4, num_tables);
506            
507             /* searchRange, entrySelector, rangeShift */
508 0           power = 1; selector = 0;
509 0 0         while (power * 2 <= num_tables) { power *= 2; selector++; }
510 0           pdfmake_write_be16(result + 6, power * 16);
511 0           pdfmake_write_be16(result + 8, selector);
512 0           pdfmake_write_be16(result + 10, num_tables * 16 - power * 16);
513            
514             /* Table directory entries */
515 0           tables[0].tag = TAG('c','m','a','p'); tables[0].data = cmap; tables[0].len = cmap_len;
516 0           tables[1].tag = TAG('g','l','y','f'); tables[1].data = glyf->data; tables[1].len = glyf->len;
517 0           tables[2].tag = TAG('h','e','a','d'); tables[2].data = head; tables[2].len = head_len;
518 0           tables[3].tag = TAG('h','h','e','a'); tables[3].data = hhea; tables[3].len = hhea_len;
519 0           tables[4].tag = TAG('h','m','t','x'); tables[4].data = hmtx; tables[4].len = hmtx_len;
520 0           tables[5].tag = TAG('l','o','c','a'); tables[5].data = loca; tables[5].len = loca_len;
521 0           tables[6].tag = TAG('m','a','x','p'); tables[6].data = maxp; tables[6].len = maxp_len;
522 0           tables[7].tag = TAG('p','o','s','t'); tables[7].data = post; tables[7].len = post_len;
523            
524 0           dir = result + 12;
525 0           offset = header_size;
526            
527 0 0         for (i = 0; i < num_tables; i++) {
528 0           pdfmake_write_be32(dir, tables[i].tag);
529 0           pdfmake_write_be32(dir + 4, ttf_checksum(tables[i].data, tables[i].len));
530 0           pdfmake_write_be32(dir + 8, offset);
531 0           pdfmake_write_be32(dir + 12, tables[i].len);
532            
533 0           memcpy(result + offset, tables[i].data, tables[i].len);
534 0           offset += ALIGN4(tables[i].len);
535 0           dir += 16;
536             }
537            
538             /* Update head checkSumAdjustment */
539 0           file_checksum = ttf_checksum(result, total_size);
540 0           head_offset = 0;
541 0 0         for (i = 0; i < num_tables; i++) {
542 0 0         if (tables[i].tag == TAG('h','e','a','d')) {
543 0           head_offset = pdfmake_read_be32(result + 12 + i * 16 + 8);
544 0           break;
545             }
546             }
547 0           pdfmake_write_be32(result + head_offset + 8, 0xB1B0AFBA - file_checksum);
548            
549             /* Copy to output buffer */
550 0           pdfmake_buf_append(out_buf, result, total_size);
551            
552             /* Cleanup */
553 0           free(result);
554 0           free(loca); free(hmtx); free(cmap); free(head);
555 0           free(maxp); free(hhea); free(post);
556 0           free_glyf_result(glyf);
557 0           free_glyph_map(map);
558            
559 0           return PDFMAKE_OK;
560            
561             #undef ALIGN4
562             }