File Coverage

src/pdfmake_font_glyph.c
Criterion Covered Total %
statement 0 262 0.0
branch 0 162 0.0
condition n/a
subroutine n/a
pod n/a
total 0 424 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_font_glyph.c - Glyph outline caching and loading
3             *
4             * Manages glyph outline extraction from TrueType and CFF fonts.
5             * Caches loaded outlines for efficient text rendering.
6             */
7              
8             #include "pdfmake_text.h"
9             #include "pdfmake_font.h"
10             #include "pdfmake_render.h"
11             #include "pdfmake_arena.h"
12             #include "pdfmake_internal.h"
13             #include
14             #include
15              
16             /*============================================================================
17             * Glyph Cache Management
18             *==========================================================================*/
19              
20 0           pdfmake_glyph_cache_t *pdfmake_glyph_cache_create(
21             pdfmake_font_t *font,
22             pdfmake_arena_t *arena)
23             {
24             pdfmake_glyph_cache_t *cache;
25             size_t glyph_count;
26              
27 0 0         if (!font || !arena) return NULL;
    0          
28            
29 0           cache = pdfmake_arena_alloc(arena,
30             sizeof(pdfmake_glyph_cache_t));
31 0 0         if (!cache) return NULL;
32            
33             /* Get glyph count from font */
34 0           glyph_count = 0;
35 0 0         if (font->type == PDFMAKE_FONT_TRUETYPE ||
36 0 0         font->type == PDFMAKE_FONT_CID_TRUETYPE) {
37 0 0         if (font->ttf) {
38 0           glyph_count = font->ttf->num_glyphs;
39             }
40             } else {
41             /* Standard 14 fonts - use 256 as max char code */
42 0           glyph_count = 256;
43             }
44            
45 0 0         if (glyph_count == 0) {
46 0           glyph_count = 256; /* Fallback */
47             }
48            
49 0           cache->glyph_count = glyph_count;
50 0           cache->loaded_count = 0;
51 0           cache->arena = arena;
52            
53             /* Allocate glyph array */
54 0           cache->glyphs = pdfmake_arena_alloc(arena,
55             glyph_count * sizeof(pdfmake_glyph_outline_t));
56 0 0         if (!cache->glyphs) return NULL;
57            
58             /* Initialize all glyphs as unloaded */
59 0           memset(cache->glyphs, 0, glyph_count * sizeof(pdfmake_glyph_outline_t));
60            
61 0           return cache;
62             }
63              
64 0           void pdfmake_glyph_cache_free(pdfmake_glyph_cache_t *cache)
65             {
66             size_t i;
67             /* If using arena, paths are freed with arena */
68             /* This is mainly for non-arena cleanup */
69 0 0         if (!cache) return;
70            
71 0 0         if (cache->glyphs && !cache->arena) {
    0          
72 0 0         for (i = 0; i < cache->glyph_count; i++) {
73 0 0         if (cache->glyphs[i].path) {
74 0           pdfmake_path_destroy(cache->glyphs[i].path);
75             }
76             }
77 0           free(cache->glyphs);
78             }
79             }
80              
81 0           pdfmake_glyph_outline_t *pdfmake_glyph_get(
82             pdfmake_glyph_cache_t *cache,
83             pdfmake_font_t *font,
84             uint16_t glyph_id)
85             {
86             pdfmake_glyph_outline_t *outline;
87              
88 0 0         if (!cache || !font) return NULL;
    0          
89 0 0         if (glyph_id >= cache->glyph_count) return NULL;
90            
91 0           outline = &cache->glyphs[glyph_id];
92            
93             /* Load if not already loaded */
94 0 0         if (!outline->loaded) {
95 0           pdfmake_text_err_t err = pdfmake_glyph_load(cache, font, glyph_id);
96 0 0         if (err != PDFMAKE_TEXT_OK) {
97             /* Mark as loaded but empty to avoid repeated attempts */
98 0           outline->loaded = 1;
99 0           outline->path = NULL;
100             }
101             }
102            
103 0           return outline;
104             }
105              
106 0           pdfmake_text_err_t pdfmake_glyph_load(
107             pdfmake_glyph_cache_t *cache,
108             pdfmake_font_t *font,
109             uint16_t glyph_id)
110             {
111             pdfmake_glyph_outline_t *outline;
112             pdfmake_text_err_t err;
113              
114 0 0         if (!cache || !font) return PDFMAKE_TEXT_ERR_NULL;
    0          
115 0 0         if (glyph_id >= cache->glyph_count) return PDFMAKE_TEXT_ERR_GLYPH_NOT_FOUND;
116            
117 0           outline = &cache->glyphs[glyph_id];
118            
119             /* Already loaded? */
120 0 0         if (outline->loaded) return PDFMAKE_TEXT_OK;
121            
122 0           outline->glyph_id = glyph_id;
123            
124             /* Load based on font type */
125 0           switch (font->type) {
126 0           case PDFMAKE_FONT_TRUETYPE:
127             case PDFMAKE_FONT_CID_TRUETYPE:
128 0           err = pdfmake_ttf_load_glyph(outline, font, glyph_id, cache->arena);
129 0           break;
130            
131 0           case PDFMAKE_FONT_TYPE1:
132             /* Standard 14 fonts don't have glyph outlines in data */
133             /* Would need substitution font - return empty for now */
134 0           outline->loaded = 1;
135 0           outline->path = NULL;
136 0           outline->advance_width = pdfmake_std14_width(font->std14_id, glyph_id);
137 0           err = PDFMAKE_TEXT_OK;
138 0           break;
139            
140 0           default:
141 0           err = PDFMAKE_TEXT_ERR_UNSUPPORTED;
142 0           break;
143             }
144            
145 0 0         if (err == PDFMAKE_TEXT_OK) {
146 0           outline->loaded = 1;
147 0           cache->loaded_count++;
148             }
149            
150 0           return err;
151             }
152              
153             /*============================================================================
154             * TrueType Glyph Loading
155             *==========================================================================*/
156              
157             /* TrueType glyf flags */
158             #define TTF_ON_CURVE 0x01
159             #define TTF_X_SHORT 0x02
160             #define TTF_Y_SHORT 0x04
161             #define TTF_REPEAT 0x08
162             #define TTF_X_SAME 0x10
163             #define TTF_Y_SAME 0x20
164              
165             /* Composite glyph flags */
166             #define TTF_ARG_1_AND_2_ARE_WORDS 0x0001
167             #define TTF_ARGS_ARE_XY_VALUES 0x0002
168             #define TTF_ROUND_XY_TO_GRID 0x0004
169             #define TTF_WE_HAVE_A_SCALE 0x0008
170             #define TTF_MORE_COMPONENTS 0x0020
171             #define TTF_WE_HAVE_AN_X_AND_Y_SCALE 0x0040
172             #define TTF_WE_HAVE_A_TWO_BY_TWO 0x0080
173             #define TTF_WE_HAVE_INSTRUCTIONS 0x0100
174             #define TTF_USE_MY_METRICS 0x0200
175              
176             /*
177             * Get glyph data offset from loca table.
178             */
179 0           static size_t get_glyph_offset(pdfmake_font_t *font, uint16_t glyph_id,
180             size_t *out_len)
181             {
182 0           pdfmake_ttf_t *ttf = font->ttf;
183             const uint8_t *loca;
184             size_t offset, next_offset;
185              
186 0 0         if (!ttf || glyph_id >= ttf->num_glyphs) return 0;
    0          
187            
188 0           loca = ttf->data + ttf->loca.offset;
189            
190 0 0         if (ttf->index_to_loc_format == 0) {
191             /* Short format: offset / 2 stored as uint16 */
192 0           offset = pdfmake_read_be16(loca + glyph_id * 2) * 2;
193 0           next_offset = pdfmake_read_be16(loca + (glyph_id + 1) * 2) * 2;
194             } else {
195             /* Long format: offset stored as uint32 */
196 0           offset = pdfmake_read_be32(loca + glyph_id * 4);
197 0           next_offset = pdfmake_read_be32(loca + (glyph_id + 1) * 4);
198             }
199            
200 0 0         if (out_len) {
201 0           *out_len = next_offset - offset;
202             }
203            
204 0           return offset;
205             }
206              
207             /*
208             * Convert quadratic Bezier (P0, P1, P2) to cubic (P0, C1, C2, P3).
209             * C1 = P0 + 2/3 * (P1 - P0)
210             * C2 = P2 + 2/3 * (P1 - P2)
211             */
212 0           static void quadratic_to_cubic(
213             double x0, double y0, /* P0 - start point */
214             double x1, double y1, /* P1 - control point */
215             double x2, double y2, /* P2 - end point */
216             double *cx1, double *cy1, /* C1 - first cubic control */
217             double *cx2, double *cy2) /* C2 - second cubic control */
218             {
219 0           *cx1 = x0 + (2.0/3.0) * (x1 - x0);
220 0           *cy1 = y0 + (2.0/3.0) * (y1 - y0);
221 0           *cx2 = x2 + (2.0/3.0) * (x1 - x2);
222 0           *cy2 = y2 + (2.0/3.0) * (y1 - y2);
223 0           }
224              
225 0           pdfmake_text_err_t pdfmake_ttf_load_glyph(
226             pdfmake_glyph_outline_t *outline,
227             pdfmake_font_t *font,
228             uint16_t glyph_id,
229             pdfmake_arena_t *arena)
230             {
231             pdfmake_ttf_t *ttf;
232             size_t glyph_len;
233             size_t glyph_offset;
234             const uint8_t *glyph_data;
235             const uint8_t *end;
236             int16_t num_contours;
237             const uint8_t *p;
238             pdfmake_path_t *path;
239             uint16_t *end_pts;
240             uint16_t i;
241             uint16_t num_points;
242             uint16_t inst_len;
243             uint8_t *flags;
244             uint8_t flag;
245             uint8_t repeat;
246             uint8_t j;
247             int16_t *x_coords;
248             int16_t *y_coords;
249             int16_t x;
250             int16_t y;
251             int16_t dx;
252             int16_t dy;
253             uint16_t pt;
254             int c;
255             uint16_t contour_end;
256             uint16_t contour_start;
257             uint16_t contour_len;
258             int first_on;
259             uint16_t idx;
260             uint16_t next_idx;
261             uint16_t start_idx;
262             double cx, cy;
263             double nx, ny;
264             double ex, ey;
265             double mx, my;
266             double x0, y0;
267             double c1x, c1y, c2x, c2y;
268             int on_curve;
269             int next_on;
270              
271 0 0         if (!outline || !font || !font->ttf || !arena) {
    0          
    0          
    0          
272 0           return PDFMAKE_TEXT_ERR_NULL;
273             }
274            
275 0           ttf = font->ttf;
276            
277             /* Get glyph metrics first */
278 0           outline->advance_width = pdfmake_ttf_glyph_advance(ttf, glyph_id);
279            
280             /* Get glyph data location */
281 0           glyph_offset = get_glyph_offset(font, glyph_id, &glyph_len);
282            
283 0 0         if (glyph_len == 0) {
284             /* Empty glyph (e.g., space) */
285 0           outline->path = NULL;
286 0           outline->x_min = outline->y_min = outline->x_max = outline->y_max = 0;
287 0           return PDFMAKE_TEXT_OK;
288             }
289            
290 0           glyph_data = ttf->data + ttf->glyf.offset + glyph_offset;
291 0           end = glyph_data + glyph_len;
292            
293             /* Read glyph header */
294 0 0         if (glyph_len < 10) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
295            
296 0           num_contours = pdfmake_read_sbe16(glyph_data);
297 0           outline->x_min = pdfmake_read_sbe16(glyph_data + 2);
298 0           outline->y_min = pdfmake_read_sbe16(glyph_data + 4);
299 0           outline->x_max = pdfmake_read_sbe16(glyph_data + 6);
300 0           outline->y_max = pdfmake_read_sbe16(glyph_data + 8);
301            
302 0           p = glyph_data + 10;
303            
304 0 0         if (num_contours < 0) {
305             /* Composite glyph */
306 0           outline->composite = 1;
307             /* For now, return empty path - composite handling is complex */
308 0           outline->path = NULL;
309 0           return PDFMAKE_TEXT_OK;
310             }
311            
312 0 0         if (num_contours == 0) {
313             /* No outlines */
314 0           outline->path = NULL;
315 0           return PDFMAKE_TEXT_OK;
316             }
317            
318             /* Create path */
319 0           path = pdfmake_path_create();
320 0 0         if (!path) return PDFMAKE_TEXT_ERR_MEMORY;
321            
322             /* Read end points of each contour */
323 0 0         if (p + num_contours * 2 > end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
324            
325 0           end_pts = pdfmake_arena_alloc(arena, num_contours * sizeof(uint16_t));
326 0 0         if (!end_pts) return PDFMAKE_TEXT_ERR_MEMORY;
327            
328 0 0         for (i = 0; i < num_contours; i++) {
329 0           end_pts[i] = pdfmake_read_be16(p);
330 0           p += 2;
331             }
332            
333 0           num_points = end_pts[num_contours - 1] + 1;
334            
335             /* Skip instructions */
336 0 0         if (p + 2 > end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
337 0           inst_len = pdfmake_read_be16(p);
338 0           p += 2 + inst_len;
339            
340 0 0         if (p > end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
341            
342             /* Read flags */
343 0           flags = pdfmake_arena_alloc(arena, num_points);
344 0 0         if (!flags) return PDFMAKE_TEXT_ERR_MEMORY;
345            
346 0 0         for (i = 0; i < num_points; ) {
347 0 0         if (p >= end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
348 0           flag = *p++;
349 0           flags[i++] = flag;
350            
351 0 0         if (flag & TTF_REPEAT) {
352 0 0         if (p >= end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
353 0           repeat = *p++;
354 0 0         for (j = 0; j < repeat && i < num_points; j++) {
    0          
355 0           flags[i++] = flag;
356             }
357             }
358             }
359            
360             /* Read X coordinates */
361 0           x_coords = pdfmake_arena_alloc(arena, num_points * sizeof(int16_t));
362 0 0         if (!x_coords) return PDFMAKE_TEXT_ERR_MEMORY;
363            
364 0           x = 0;
365 0 0         for (i = 0; i < num_points; i++) {
366 0           flag = flags[i];
367 0 0         if (flag & TTF_X_SHORT) {
368 0 0         if (p >= end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
369 0           dx = *p++;
370 0 0         x += (flag & TTF_X_SAME) ? dx : -dx;
371 0 0         } else if (!(flag & TTF_X_SAME)) {
372 0 0         if (p + 2 > end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
373 0           x += pdfmake_read_sbe16(p);
374 0           p += 2;
375             }
376             /* else: same as previous (delta = 0) */
377 0           x_coords[i] = x;
378             }
379            
380             /* Read Y coordinates */
381 0           y_coords = pdfmake_arena_alloc(arena, num_points * sizeof(int16_t));
382 0 0         if (!y_coords) return PDFMAKE_TEXT_ERR_MEMORY;
383            
384 0           y = 0;
385 0 0         for (i = 0; i < num_points; i++) {
386 0           flag = flags[i];
387 0 0         if (flag & TTF_Y_SHORT) {
388 0 0         if (p >= end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
389 0           dy = *p++;
390 0 0         y += (flag & TTF_Y_SAME) ? dy : -dy;
391 0 0         } else if (!(flag & TTF_Y_SAME)) {
392 0 0         if (p + 2 > end) return PDFMAKE_TEXT_ERR_PARSE_ERROR;
393 0           y += pdfmake_read_sbe16(p);
394 0           p += 2;
395             }
396 0           y_coords[i] = y;
397             }
398            
399             /* Convert points to path */
400 0           pt = 0;
401 0 0         for (c = 0; c < num_contours; c++) {
402 0           contour_end = end_pts[c];
403 0           contour_start = pt;
404 0           contour_len = contour_end - contour_start + 1;
405            
406 0 0         if (contour_len < 2) {
407 0           pt = contour_end + 1;
408 0           continue;
409             }
410            
411             /* Find first on-curve point to start */
412 0           first_on = -1;
413 0 0         for (i = 0; i < contour_len; i++) {
414 0 0         if (flags[contour_start + i] & TTF_ON_CURVE) {
415 0           first_on = i;
416 0           break;
417             }
418             }
419            
420 0 0         if (first_on < 0) {
421             /* All off-curve: insert implicit on-curve at midpoint of first two */
422 0           mx = (x_coords[contour_start] + x_coords[contour_start + 1]) / 2.0;
423 0           my = (y_coords[contour_start] + y_coords[contour_start + 1]) / 2.0;
424 0           pdfmake_path_move_to(path, mx, my);
425 0           first_on = 0;
426             } else {
427 0           start_idx = contour_start + first_on;
428 0           pdfmake_path_move_to(path, x_coords[start_idx], y_coords[start_idx]);
429             }
430            
431             /* Process contour points */
432 0 0         for (i = 0; i < contour_len; i++) {
433 0           idx = contour_start + ((first_on + 1 + i) % contour_len);
434 0           next_idx = contour_start + ((first_on + 2 + i) % contour_len);
435            
436 0           cx = x_coords[idx];
437 0           cy = y_coords[idx];
438 0           on_curve = flags[idx] & TTF_ON_CURVE;
439            
440 0 0         if (on_curve) {
441 0           pdfmake_path_line_to(path, cx, cy);
442             } else {
443             /* Off-curve point: quadratic Bezier control */
444 0           nx = x_coords[next_idx];
445 0           ny = y_coords[next_idx];
446 0           next_on = flags[next_idx] & TTF_ON_CURVE;
447            
448 0 0         if (next_on) {
449 0           ex = nx;
450 0           ey = ny;
451 0           i++; /* Skip next point, we've used it */
452             } else {
453             /* Implicit on-curve point at midpoint */
454 0           ex = (cx + nx) / 2.0;
455 0           ey = (cy + ny) / 2.0;
456             }
457            
458             /* Get current point */
459 0           x0 = path->current.x;
460 0           y0 = path->current.y;
461            
462             /* Convert quadratic to cubic */
463 0           quadratic_to_cubic(x0, y0, cx, cy, ex, ey, &c1x, &c1y, &c2x, &c2y);
464            
465 0           pdfmake_path_curve_to(path, c1x, c1y, c2x, c2y, ex, ey);
466             }
467             }
468            
469 0           pdfmake_path_close(path);
470 0           pt = contour_end + 1;
471             }
472            
473 0           outline->path = path;
474 0           return PDFMAKE_TEXT_OK;
475             }
476              
477 0           pdfmake_text_err_t pdfmake_ttf_load_composite_glyph(
478             pdfmake_glyph_outline_t *outline,
479             pdfmake_font_t *font,
480             uint16_t glyph_id,
481             pdfmake_glyph_cache_t *cache,
482             pdfmake_arena_t *arena)
483             {
484             /* TODO: Implement composite glyph loading */
485             /* This involves recursively loading component glyphs and applying transforms */
486             (void)outline;
487             (void)font;
488             (void)glyph_id;
489             (void)cache;
490             (void)arena;
491 0           return PDFMAKE_TEXT_ERR_UNSUPPORTED;
492             }
493              
494             /* CFF glyph loading implemented in pdfmake_font_cff.c */
495              
496             /*============================================================================
497             * Path Utilities
498             *==========================================================================*/
499              
500 0           void pdfmake_path_transform(pdfmake_path_t *path, const double m[6])
501             {
502             size_t i;
503             int p;
504              
505 0 0         if (!path || !m) return;
    0          
506            
507 0 0         for (i = 0; i < path->seg_count; i++) {
508 0           pdfmake_path_seg_t *seg = &path->segs[i];
509 0           int num_pts = 0;
510            
511 0           switch (seg->op) {
512 0           case PDFMAKE_PATH_MOVE:
513             case PDFMAKE_PATH_LINE:
514 0           num_pts = 1;
515 0           break;
516 0           case PDFMAKE_PATH_CURVE:
517 0           num_pts = 3;
518 0           break;
519 0           case PDFMAKE_PATH_CLOSE:
520 0           num_pts = 0;
521 0           break;
522             }
523            
524 0 0         for (p = 0; p < num_pts; p++) {
525 0           double x = seg->pts[p].x;
526 0           double y = seg->pts[p].y;
527 0           seg->pts[p].x = m[0] * x + m[2] * y + m[4];
528 0           seg->pts[p].y = m[1] * x + m[3] * y + m[5];
529             }
530             }
531            
532             /* Update current point */
533 0 0         if (path->has_current) {
534 0           double x = path->current.x;
535 0           double y = path->current.y;
536 0           path->current.x = m[0] * x + m[2] * y + m[4];
537 0           path->current.y = m[1] * x + m[3] * y + m[5];
538             }
539            
540             /* Update subpath start */
541 0 0         if (path->has_subpath) {
542 0           double x = path->subpath_start.x;
543 0           double y = path->subpath_start.y;
544 0           path->subpath_start.x = m[0] * x + m[2] * y + m[4];
545 0           path->subpath_start.y = m[1] * x + m[3] * y + m[5];
546             }
547             }
548              
549 0           pdfmake_path_t *pdfmake_path_transform_copy(
550             pdfmake_path_t *src,
551             const double m[6],
552             pdfmake_arena_t *arena)
553             {
554             pdfmake_path_t *dst;
555             size_t i;
556              
557 0 0         if (!src || !m || !arena) return NULL;
    0          
    0          
558            
559             /* Create new path */
560 0           dst = pdfmake_path_create();
561 0 0         if (!dst) return NULL;
562            
563             /* Copy and transform segments */
564 0 0         for (i = 0; i < src->seg_count; i++) {
565 0           pdfmake_path_seg_t *seg = &src->segs[i];
566            
567 0           switch (seg->op) {
568 0           case PDFMAKE_PATH_MOVE: {
569 0           double x = m[0] * seg->pts[0].x + m[2] * seg->pts[0].y + m[4];
570 0           double y = m[1] * seg->pts[0].x + m[3] * seg->pts[0].y + m[5];
571 0           pdfmake_path_move_to(dst, x, y);
572 0           break;
573             }
574 0           case PDFMAKE_PATH_LINE: {
575 0           double x = m[0] * seg->pts[0].x + m[2] * seg->pts[0].y + m[4];
576 0           double y = m[1] * seg->pts[0].x + m[3] * seg->pts[0].y + m[5];
577 0           pdfmake_path_line_to(dst, x, y);
578 0           break;
579             }
580 0           case PDFMAKE_PATH_CURVE: {
581 0           double x1 = m[0] * seg->pts[0].x + m[2] * seg->pts[0].y + m[4];
582 0           double y1 = m[1] * seg->pts[0].x + m[3] * seg->pts[0].y + m[5];
583 0           double x2 = m[0] * seg->pts[1].x + m[2] * seg->pts[1].y + m[4];
584 0           double y2 = m[1] * seg->pts[1].x + m[3] * seg->pts[1].y + m[5];
585 0           double x3 = m[0] * seg->pts[2].x + m[2] * seg->pts[2].y + m[4];
586 0           double y3 = m[1] * seg->pts[2].x + m[3] * seg->pts[2].y + m[5];
587 0           pdfmake_path_curve_to(dst, x1, y1, x2, y2, x3, y3);
588 0           break;
589             }
590 0           case PDFMAKE_PATH_CLOSE:
591 0           pdfmake_path_close(dst);
592 0           break;
593             }
594             }
595            
596 0           return dst;
597             }