File Coverage

src/pdfmake_cmap.c
Criterion Covered Total %
statement 242 287 84.3
branch 168 326 51.5
condition n/a
subroutine n/a
pod n/a
total 410 613 66.8


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_cmap.c — PDF ToUnicode CMap parser implementation.
3             */
4              
5             #include "pdfmake_cmap.h"
6             #include
7             #include
8              
9             /*============================================================================
10             * Data structures
11             *==========================================================================*/
12              
13             typedef struct {
14             uint32_t code;
15             uint8_t uni_count; /* 1..PDFMAKE_CMAP_MAX_UNI */
16             uint32_t uni[PDFMAKE_CMAP_MAX_UNI];
17             } cmap_entry_t;
18              
19             typedef struct {
20             uint32_t lo;
21             uint32_t hi;
22             uint32_t start_uni; /* incremented per code in range */
23             } cmap_range_t;
24              
25             struct pdfmake_cmap {
26             /* Sorted single-code entries */
27             cmap_entry_t *entries;
28             size_t entry_count;
29             size_t entry_cap;
30              
31             /* Sorted range entries (lo ≤ code ≤ hi, unicode = start_uni + (code - lo)) */
32             cmap_range_t *ranges;
33             size_t range_count;
34             size_t range_cap;
35              
36             /* Per-range unicode override arrays (for array form of bfrange):
37             * index into this pool from range->start_uni when -1 is used.
38             * Simpler: we store array-form ranges as expanded entries. */
39              
40             int code_width; /* 1 or 2 */
41             pdfmake_arena_t *arena;
42             };
43              
44             /*============================================================================
45             * Internal helpers — tokenizer
46             *==========================================================================*/
47              
48             typedef struct {
49             const uint8_t *p;
50             const uint8_t *end;
51             } cmap_tok_t;
52              
53 1657           static void tok_skip_ws(cmap_tok_t *t) {
54 2458 100         while (t->p < t->end) {
55 2439           uint8_t c = *t->p;
56 2439 100         if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f') {
    50          
    50          
    100          
    50          
57 801           t->p++;
58 1638 50         } else if (c == '%') {
59             /* Comment to end of line */
60 0 0         while (t->p < t->end && *t->p != '\n' && *t->p != '\r')
    0          
    0          
61 0           t->p++;
62             } else {
63 1638           break;
64             }
65             }
66 1657           }
67              
68 0           static int is_hex(uint8_t c) {
69 0 0         return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
    0          
    0          
    0          
    0          
    0          
70             }
71              
72 384           static int hex_val(uint8_t c) {
73 384 50         if (c >= '0' && c <= '9') return c - '0';
    100          
74 13 50         if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    0          
75 13 50         if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    50          
76 0           return -1;
77             }
78              
79             /* Parse a hex string <...> into a sequence of big-endian bytes.
80             * out must have at least (len/2) slots.
81             * Returns number of bytes written, or -1 on error.
82             * Leaves t->p pointing past the closing '>'. */
83 145           static int tok_hex_string(cmap_tok_t *t, uint8_t *out, size_t out_cap) {
84             size_t written;
85             int nibble_hi;
86             uint8_t c;
87             int v;
88              
89 145           tok_skip_ws(t);
90 145 50         if (t->p >= t->end || *t->p != '<') return -1;
    50          
91 145           t->p++; /* skip '<' */
92              
93 145           written = 0;
94 145           nibble_hi = -1;
95              
96 531 50         while (t->p < t->end && *t->p != '>') {
    100          
97 386           c = *t->p++;
98 386 100         if (c == ' ' || c == '\t' || c == '\r' || c == '\n') continue;
    50          
    50          
    50          
99 384           v = hex_val(c);
100 384 50         if (v < 0) return -1;
101 384 100         if (nibble_hi < 0) {
102 192           nibble_hi = v;
103             } else {
104 192 50         if (written >= out_cap) return -1;
105 192           out[written++] = (uint8_t)((nibble_hi << 4) | v);
106 192           nibble_hi = -1;
107             }
108             }
109             /* Handle odd nibble count — pad with 0 per PDF spec */
110 145 50         if (nibble_hi >= 0) {
111 0 0         if (written >= out_cap) return -1;
112 0           out[written++] = (uint8_t)(nibble_hi << 4);
113             }
114 145 50         if (t->p >= t->end || *t->p != '>') return -1;
    50          
115 145           t->p++; /* skip '>' */
116              
117 145           return (int)written;
118             }
119              
120             /* Parse a hex string as a single big-endian integer (1-4 bytes).
121             * Returns byte width on success, -1 on failure. */
122 107           static int tok_hex_int(cmap_tok_t *t, uint32_t *out_value) {
123             uint8_t buf[8];
124             int n;
125             uint32_t v;
126             int i;
127              
128 107           n = tok_hex_string(t, buf, sizeof(buf));
129 107 50         if (n < 0 || n > 4) return -1;
    50          
130 107           v = 0;
131 219 100         for (i = 0; i < n; i++) {
132 112           v = (v << 8) | buf[i];
133             }
134 107           *out_value = v;
135 107           return n;
136             }
137              
138             /* Parse a hex string as a sequence of BE16 Unicode code units.
139             * Handles surrogate pairs → codepoint collapse.
140             * Returns count of codepoints (≤ PDFMAKE_CMAP_MAX_UNI), -1 on error. */
141 38           static int tok_hex_unicode(cmap_tok_t *t, uint32_t *out, size_t out_cap) {
142             uint8_t buf[32];
143             int n;
144             size_t written;
145             int i;
146             uint16_t hi;
147             uint32_t cp;
148             uint16_t lo;
149              
150 38           n = tok_hex_string(t, buf, sizeof(buf));
151 38 50         if (n < 0 || (n % 2) != 0) return -1;
    50          
152              
153 38           written = 0;
154 38           i = 0;
155 78 100         while (i + 1 < n) {
156 40           hi = ((uint16_t)buf[i] << 8) | buf[i + 1];
157 40           i += 2;
158 40           cp = hi;
159              
160             /* Surrogate pair? */
161 40 100         if (hi >= 0xD800 && hi <= 0xDBFF && i + 1 < n) {
    50          
    0          
162 0           lo = ((uint16_t)buf[i] << 8) | buf[i + 1];
163 0 0         if (lo >= 0xDC00 && lo <= 0xDFFF) {
    0          
164 0           cp = 0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00);
165 0           i += 2;
166             }
167             }
168              
169 40 50         if (written >= out_cap) return -1;
170 40           out[written++] = cp;
171             }
172 38           return (int)written;
173             }
174              
175             /* Parse an integer count (e.g. "42 beginbfchar"). */
176 559           static int tok_int(cmap_tok_t *t, long *out) {
177             const uint8_t *start;
178             int negative;
179             long v;
180             int any;
181              
182 559           tok_skip_ws(t);
183 559           start = t->p;
184 559           negative = 0;
185 559 50         if (t->p < t->end && (*t->p == '-' || *t->p == '+')) {
    50          
    50          
186 0 0         if (*t->p == '-') negative = 1;
187 0           t->p++;
188             }
189 559           v = 0;
190 559           any = 0;
191 654 50         while (t->p < t->end && *t->p >= '0' && *t->p <= '9') {
    100          
    100          
192 95           v = v * 10 + (*t->p - '0');
193 95           t->p++;
194 95           any = 1;
195             }
196 559 100         if (!any) { t->p = start; return -1; }
197 76 50         *out = negative ? -v : v;
198 76           return 0;
199             }
200              
201             /* Check if upcoming tokens are a keyword (followed by delimiter). Advances
202             * the tokenizer past the keyword on match; leaves position unchanged otherwise. */
203 286           static int tok_match_keyword(cmap_tok_t *t, const char *kw) {
204             const uint8_t *save;
205             size_t kwlen;
206             uint8_t c;
207              
208 286           save = t->p;
209 286           tok_skip_ws(t);
210 286           kwlen = strlen(kw);
211 286 50         if ((size_t)(t->end - t->p) < kwlen) { t->p = save; return 0; }
212 286 100         if (memcmp(t->p, kw, kwlen) != 0) { t->p = save; return 0; }
213             /* Must be followed by delimiter */
214 76 50         if (t->p + kwlen < t->end) {
215 76           c = t->p[kwlen];
216 76 100         if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
    50          
    50          
    50          
    0          
    0          
217 0 0         c == '<' || c == '[' || c == '/' || c == '%')) {
    0          
218 0           t->p = save;
219 0           return 0;
220             }
221             }
222 76           t->p += kwlen;
223 76           return 1;
224             }
225              
226             /*============================================================================
227             * CMap population
228             *==========================================================================*/
229              
230 7           static int cmap_reserve_entries(pdfmake_cmap_t *cm, size_t need) {
231             size_t new_cap;
232             cmap_entry_t *n;
233              
234 7 100         if (need <= cm->entry_cap) return 0;
235 2 50         new_cap = cm->entry_cap ? cm->entry_cap : 64;
236 2 50         while (new_cap < need) new_cap *= 2;
237 2           n = realloc(cm->entries, new_cap * sizeof(cmap_entry_t));
238 2 50         if (!n) return -1;
239 2           cm->entries = n;
240 2           cm->entry_cap = new_cap;
241 2           return 0;
242             }
243              
244 31           static int cmap_reserve_ranges(pdfmake_cmap_t *cm, size_t need) {
245             size_t new_cap;
246             cmap_range_t *n;
247              
248 31 100         if (need <= cm->range_cap) return 0;
249 17 50         new_cap = cm->range_cap ? cm->range_cap : 16;
250 17 50         while (new_cap < need) new_cap *= 2;
251 17           n = realloc(cm->ranges, new_cap * sizeof(cmap_range_t));
252 17 50         if (!n) return -1;
253 17           cm->ranges = n;
254 17           cm->range_cap = new_cap;
255 17           return 0;
256             }
257              
258 7           static int cmap_add_entry(pdfmake_cmap_t *cm, uint32_t code,
259             const uint32_t *uni, size_t uni_count) {
260             cmap_entry_t *e;
261             size_t i;
262              
263 7 50         if (uni_count == 0 || uni_count > PDFMAKE_CMAP_MAX_UNI) return -1;
    50          
264 7 50         if (cmap_reserve_entries(cm, cm->entry_count + 1) < 0) return -1;
265 7           e = &cm->entries[cm->entry_count++];
266 7           e->code = code;
267 7           e->uni_count = (uint8_t)uni_count;
268 16 100         for (i = 0; i < uni_count; i++) e->uni[i] = uni[i];
269 7           return 0;
270             }
271              
272 31           static int cmap_add_range(pdfmake_cmap_t *cm,
273             uint32_t lo, uint32_t hi, uint32_t start_uni) {
274             cmap_range_t *r;
275              
276 31 50         if (hi < lo) return -1;
277 31 50         if (cmap_reserve_ranges(cm, cm->range_count + 1) < 0) return -1;
278 31           r = &cm->ranges[cm->range_count++];
279 31           r->lo = lo;
280 31           r->hi = hi;
281 31           r->start_uni = start_uni;
282 31           return 0;
283             }
284              
285             /*============================================================================
286             * Operator handlers
287             *==========================================================================*/
288              
289             /* Parse: N beginbfchar (code) (uni) ... endbfchar
290             * or N begincidchar (code) (uni) ... endcidchar */
291 2           static int parse_bfchar_section(cmap_tok_t *t, pdfmake_cmap_t *cm, long count) {
292             long i;
293             uint32_t code;
294             int code_w;
295             uint32_t uni[PDFMAKE_CMAP_MAX_UNI];
296             int n;
297              
298 9 100         for (i = 0; i < count; i++) {
299 7           code_w = tok_hex_int(t, &code);
300 7 50         if (code_w < 0) return -1;
301 7 50         if (cm->code_width == 0 || code_w > cm->code_width)
    50          
302 0           cm->code_width = code_w;
303              
304 7           n = tok_hex_unicode(t, uni, PDFMAKE_CMAP_MAX_UNI);
305 7 50         if (n <= 0) return -1;
306              
307 7 50         if (cmap_add_entry(cm, code, uni, (size_t)n) < 0) return -1;
308             }
309             /* Skip the endbfchar/endcidchar keyword */
310 2           if (!tok_match_keyword(t, "endbfchar") &&
311 0           !tok_match_keyword(t, "endcidchar")) {
312 0           return -1;
313             }
314 2           return 0;
315             }
316              
317             /* Parse: N beginbfrange (lo) (hi) (uni) ... endbfrange
318             * or N beginbfrange (lo) (hi) [ ...] endbfrange
319             * Array form: each code in [lo..hi] maps to the corresponding array entry. */
320 17           static int parse_bfrange_section(cmap_tok_t *t, pdfmake_cmap_t *cm, long count) {
321             long i;
322             uint32_t lo, hi;
323             int lw, hw;
324             int w;
325             uint32_t uni[PDFMAKE_CMAP_MAX_UNI];
326             int n;
327             uint32_t c;
328             uint32_t u[PDFMAKE_CMAP_MAX_UNI];
329             int j;
330             uint32_t delta;
331             uint32_t uni2[PDFMAKE_CMAP_MAX_UNI];
332             int n2;
333              
334 48 100         for (i = 0; i < count; i++) {
335 31           lw = tok_hex_int(t, &lo);
336 31           hw = tok_hex_int(t, &hi);
337 31 50         if (lw < 0 || hw < 0) return -1;
    50          
338 31           w = lw > hw ? lw : hw;
339 31 50         if (cm->code_width == 0 || w > cm->code_width) cm->code_width = w;
    50          
340              
341 31           tok_skip_ws(t);
342 31 50         if (t->p >= t->end) return -1;
343              
344 31 50         if (*t->p == '<') {
345             /* Range form: single unicode start */
346 31           n = tok_hex_unicode(t, uni, PDFMAKE_CMAP_MAX_UNI);
347 31 50         if (n <= 0) return -1;
348              
349 31 50         if (n == 1) {
350             /* Simple single-codepoint range — store as compact range */
351 31 50         if (cmap_add_range(cm, lo, hi, uni[0]) < 0) return -1;
352             } else {
353             /* Multi-codepoint sequence: only the first code uses the full
354             * sequence; subsequent codes would increment the last codepoint
355             * per the spec, but that's rarely used. Expand as entries. */
356 0 0         for (c = lo; c <= hi; c++) {
357 0 0         for (j = 0; j < n; j++) u[j] = uni[j];
358             /* Increment the last codepoint */
359 0           delta = c - lo;
360 0 0         if (n > 0) u[n - 1] += delta;
361 0 0         if (cmap_add_entry(cm, c, u, (size_t)n) < 0) return -1;
362             }
363             }
364 0 0         } else if (*t->p == '[') {
365             /* Array form */
366 0           t->p++; /* skip '[' */
367 0 0         for (c = lo; c <= hi; c++) {
368 0           tok_skip_ws(t);
369 0 0         if (t->p >= t->end || *t->p != '<') return -1;
    0          
370 0           n2 = tok_hex_unicode(t, uni2, PDFMAKE_CMAP_MAX_UNI);
371 0 0         if (n2 <= 0) return -1;
372 0 0         if (cmap_add_entry(cm, c, uni2, (size_t)n2) < 0) return -1;
373             }
374 0           tok_skip_ws(t);
375 0 0         if (t->p >= t->end || *t->p != ']') return -1;
    0          
376 0           t->p++; /* skip ']' */
377             } else {
378 0           return -1;
379             }
380             }
381 17           if (!tok_match_keyword(t, "endbfrange") &&
382 0           !tok_match_keyword(t, "endcidrange")) {
383 0           return -1;
384             }
385 17           return 0;
386             }
387              
388             /* Parse: N begincodespacerange ... endcodespacerange
389             * We use this only to detect the maximum code width. */
390 19           static int parse_codespace_section(cmap_tok_t *t, pdfmake_cmap_t *cm, long count) {
391             long i;
392             uint32_t lo, hi;
393             int lw, hw;
394             int w;
395              
396 38 100         for (i = 0; i < count; i++) {
397 19           lw = tok_hex_int(t, &lo);
398 19           hw = tok_hex_int(t, &hi);
399 19 50         if (lw < 0 || hw < 0) return -1;
    50          
400 19           w = lw > hw ? lw : hw;
401 19 50         if (cm->code_width == 0 || w > cm->code_width) cm->code_width = w;
    0          
402             }
403 19 50         if (!tok_match_keyword(t, "endcodespacerange")) return -1;
404 19           return 0;
405             }
406              
407             /*============================================================================
408             * Sorting / lookup
409             *==========================================================================*/
410              
411 6           static int entry_cmp(const void *a, const void *b) {
412 6           uint32_t ca = ((const cmap_entry_t *)a)->code;
413 6           uint32_t cb = ((const cmap_entry_t *)b)->code;
414 6           return (ca > cb) - (ca < cb);
415             }
416              
417 14           static int range_cmp(const void *a, const void *b) {
418 14           uint32_t la = ((const cmap_range_t *)a)->lo;
419 14           uint32_t lb = ((const cmap_range_t *)b)->lo;
420 14           return (la > lb) - (la < lb);
421             }
422              
423             /*============================================================================
424             * Public API
425             *==========================================================================*/
426              
427 19           pdfmake_cmap_t *pdfmake_cmap_parse(pdfmake_arena_t *arena,
428             const uint8_t *data, size_t len) {
429             pdfmake_cmap_t *cm;
430             cmap_tok_t tok;
431             const uint8_t *save;
432             long count;
433             int rc;
434             cmap_entry_t *arena_entries;
435             cmap_range_t *arena_ranges;
436              
437 19 50         if (!arena || !data || len == 0) return NULL;
    50          
    50          
438              
439 19           cm = pdfmake_arena_alloc(arena, sizeof(*cm));
440 19 50         if (!cm) return NULL;
441 19           memset(cm, 0, sizeof(*cm));
442 19           cm->arena = arena;
443              
444 19           tok.p = data;
445 19           tok.end = data + len;
446              
447             /* Scan for operator sections; skip everything else.
448             * We look for "N begin" patterns. */
449 578 50         while (tok.p < tok.end) {
450 578           tok_skip_ws(&tok);
451 578 100         if (tok.p >= tok.end) break;
452              
453             /* Try to parse an integer followed by a keyword */
454 559           save = tok.p;
455 559 100         if (tok_int(&tok, &count) == 0 && count > 0) {
    100          
456 58           tok_skip_ws(&tok);
457 58           rc = 0;
458 114           if (tok_match_keyword(&tok, "beginbfchar") ||
459 56           tok_match_keyword(&tok, "begincidchar")) {
460 2           rc = parse_bfchar_section(&tok, cm, count);
461 95           } else if (tok_match_keyword(&tok, "beginbfrange") ||
462 39           tok_match_keyword(&tok, "begincidrange")) {
463 17           rc = parse_bfrange_section(&tok, cm, count);
464 39 100         } else if (tok_match_keyword(&tok, "begincodespacerange")) {
465 19           rc = parse_codespace_section(&tok, cm, count);
466             } else {
467             /* Not a section we care about; advance past the keyword (if any)
468             * by skipping one token so we don't loop forever. */
469 20           while (tok.p < tok.end &&
470 99 100         *tok.p != ' ' && *tok.p != '\t' &&
    50          
471 179 50         *tok.p != '\r' && *tok.p != '\n' &&
    50          
    100          
472 79 50         *tok.p != '%') {
473 79           tok.p++;
474             }
475 20           continue;
476             }
477 38 50         if (rc != 0) {
478             /* Parse error — bail out with what we have so far */
479 0           break;
480             }
481             } else {
482             /* Not a "N begin*" — skip one token and continue */
483 501           tok.p = save;
484 501           while (tok.p < tok.end &&
485 4064 100         *tok.p != ' ' && *tok.p != '\t' &&
    50          
486 7710 50         *tok.p != '\r' && *tok.p != '\n' &&
    50          
    100          
487 3563 50         *tok.p != '%') {
488 3563           tok.p++;
489             }
490 501 50         if (tok.p == save) tok.p++; /* safety: always make progress */
491             }
492             }
493              
494             /* Sort for binary-search lookup */
495 19 100         if (cm->entry_count > 1) {
496 2           qsort(cm->entries, cm->entry_count, sizeof(cmap_entry_t), entry_cmp);
497             }
498 19 100         if (cm->range_count > 1) {
499 14           qsort(cm->ranges, cm->range_count, sizeof(cmap_range_t), range_cmp);
500             }
501              
502 19 50         if (cm->code_width == 0) cm->code_width = 2; /* default to 2-byte */
503              
504             /* Copy the heap-allocated tables into the arena so the whole CMap is
505             * arena-owned and freed with the document. */
506 19 100         if (cm->entry_count > 0) {
507 2           arena_entries = pdfmake_arena_alloc(
508 2           arena, cm->entry_count * sizeof(cmap_entry_t));
509 2 50         if (arena_entries) {
510 2           memcpy(arena_entries, cm->entries,
511 2           cm->entry_count * sizeof(cmap_entry_t));
512 2           free(cm->entries);
513 2           cm->entries = arena_entries;
514 2           cm->entry_cap = cm->entry_count;
515             }
516             }
517 19 100         if (cm->range_count > 0) {
518 17           arena_ranges = pdfmake_arena_alloc(
519 17           arena, cm->range_count * sizeof(cmap_range_t));
520 17 50         if (arena_ranges) {
521 17           memcpy(arena_ranges, cm->ranges,
522 17           cm->range_count * sizeof(cmap_range_t));
523 17           free(cm->ranges);
524 17           cm->ranges = arena_ranges;
525 17           cm->range_cap = cm->range_count;
526             }
527             }
528              
529 19           return cm;
530             }
531              
532 103           int pdfmake_cmap_lookup(const pdfmake_cmap_t *cmap,
533             uint32_t code,
534             uint32_t *out,
535             size_t *out_count) {
536 103 50         if (!cmap || !out || !out_count) return 0;
    50          
    50          
537              
538             /* Binary search single-code entries first */
539 103 100         if (cmap->entry_count > 0) {
540 7           size_t lo = 0, hi = cmap->entry_count;
541 13 50         while (lo < hi) {
542 13           size_t mid = (lo + hi) / 2;
543 13           uint32_t mc = cmap->entries[mid].code;
544 13 100         if (mc == code) {
545 7           const cmap_entry_t *e = &cmap->entries[mid];
546             size_t k;
547 16 100         for (k = 0; k < e->uni_count; k++) out[k] = e->uni[k];
548 7           *out_count = e->uni_count;
549 7           return 1;
550             }
551 6 100         if (mc < code) lo = mid + 1;
552 4           else hi = mid;
553             }
554             }
555              
556             /* Binary search ranges */
557 96 50         if (cmap->range_count > 0) {
558 96           size_t lo = 0, hi = cmap->range_count;
559             /* Find last range with range.lo <= code */
560 237 100         while (lo < hi) {
561 141           size_t mid = (lo + hi) / 2;
562 141 100         if (cmap->ranges[mid].lo <= code) lo = mid + 1;
563 45           else hi = mid;
564             }
565 96 50         if (lo > 0) {
566 96           const cmap_range_t *r = &cmap->ranges[lo - 1];
567 96 50         if (code >= r->lo && code <= r->hi) {
    50          
568 96           out[0] = r->start_uni + (code - r->lo);
569 96           *out_count = 1;
570 96           return 1;
571             }
572             }
573             }
574              
575 0           return 0;
576             }
577              
578 0           int pdfmake_cmap_code_width(const pdfmake_cmap_t *cmap) {
579 0 0         return cmap ? cmap->code_width : 0;
580             }
581              
582 0           size_t pdfmake_cmap_size(const pdfmake_cmap_t *cmap) {
583 0 0         if (!cmap) return 0;
584 0           return cmap->entry_count + cmap->range_count;
585             }