File Coverage

src/pdfmake_text.c
Criterion Covered Total %
statement 0 244 0.0
branch 0 208 0.0
condition n/a
subroutine n/a
pod n/a
total 0 452 0.0


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_text.c - Text rendering engine
3             *
4             * Renders text by converting glyph outlines to paths and filling/stroking
5             * using the path rasterizer.
6             *
7             * Text state management follows PDF spec ยง9.3
8             */
9              
10             #include "pdfmake_text.h"
11             #include "pdfmake_font.h"
12             #include "pdfmake_render.h"
13             #include "pdfmake_arena.h"
14             #include
15             #include
16              
17             /*============================================================================
18             * Text State Management
19             *==========================================================================*/
20              
21 0           void pdfmake_text_state_init(pdfmake_text_state_t *ts)
22             {
23 0 0         if (!ts) return;
24            
25 0           memset(ts, 0, sizeof(*ts));
26            
27             /* Default values per PDF spec */
28 0           ts->font = NULL;
29 0           ts->font_size = 0.0;
30 0           ts->char_spacing = 0.0;
31 0           ts->word_spacing = 0.0;
32 0           ts->horiz_scale = 1.0; /* 100% */
33 0           ts->leading = 0.0;
34 0           ts->text_rise = 0.0;
35 0           ts->render_mode = PDFMAKE_TEXT_FILL;
36            
37             /* Identity text matrix */
38 0           ts->tm[0] = 1.0; ts->tm[1] = 0.0;
39 0           ts->tm[2] = 0.0; ts->tm[3] = 1.0;
40 0           ts->tm[4] = 0.0; ts->tm[5] = 0.0;
41            
42             /* Identity line matrix */
43 0           ts->tlm[0] = 1.0; ts->tlm[1] = 0.0;
44 0           ts->tlm[2] = 0.0; ts->tlm[3] = 1.0;
45 0           ts->tlm[4] = 0.0; ts->tlm[5] = 0.0;
46            
47 0           ts->cache = NULL;
48             }
49              
50 0           void pdfmake_text_state_reset(pdfmake_text_state_t *ts)
51             {
52 0 0         pdfmake_glyph_cache_t *cache = ts ? ts->cache : NULL;
53 0           pdfmake_text_state_init(ts);
54 0 0         if (ts) ts->cache = cache;
55 0           }
56              
57 0           pdfmake_text_err_t pdfmake_text_set_font(
58             pdfmake_text_state_t *ts,
59             pdfmake_font_t *font,
60             double size,
61             pdfmake_arena_t *arena)
62             {
63 0 0         if (!ts || !font) return PDFMAKE_TEXT_ERR_NULL;
    0          
64            
65 0           ts->font = font;
66 0           ts->font_size = size;
67            
68             /* Create or reuse glyph cache */
69 0 0         if (!ts->cache || ts->cache->glyph_count == 0) {
    0          
70 0           ts->cache = pdfmake_glyph_cache_create(font, arena);
71 0 0         if (!ts->cache) return PDFMAKE_TEXT_ERR_MEMORY;
72             }
73            
74 0           return PDFMAKE_TEXT_OK;
75             }
76              
77 0           void pdfmake_text_set_matrix(pdfmake_text_state_t *ts,
78             double a, double b, double c, double d,
79             double e, double f)
80             {
81 0 0         if (!ts) return;
82            
83 0           ts->tm[0] = a; ts->tm[1] = b;
84 0           ts->tm[2] = c; ts->tm[3] = d;
85 0           ts->tm[4] = e; ts->tm[5] = f;
86            
87             /* Also set line matrix (Tm sets both) */
88 0           ts->tlm[0] = a; ts->tlm[1] = b;
89 0           ts->tlm[2] = c; ts->tlm[3] = d;
90 0           ts->tlm[4] = e; ts->tlm[5] = f;
91             }
92              
93 0           void pdfmake_text_next_line(pdfmake_text_state_t *ts)
94             {
95 0 0         if (!ts) return;
96            
97             /* T* is equivalent to: 0 -TL Td */
98 0           pdfmake_text_move(ts, 0, -ts->leading);
99             }
100              
101 0           void pdfmake_text_move(pdfmake_text_state_t *ts, double tx, double ty)
102             {
103             double e, f;
104 0 0         if (!ts) return;
105            
106             /* Translate line matrix: Tlm' = [1 0 0 1 tx ty] * Tlm */
107 0           e = ts->tlm[4] + tx * ts->tlm[0] + ty * ts->tlm[2];
108 0           f = ts->tlm[5] + tx * ts->tlm[1] + ty * ts->tlm[3];
109            
110 0           ts->tlm[4] = e;
111 0           ts->tlm[5] = f;
112            
113             /* Reset text matrix to line matrix */
114 0           memcpy(ts->tm, ts->tlm, sizeof(ts->tm));
115             }
116              
117             /*============================================================================
118             * Matrix Utilities
119             *==========================================================================*/
120              
121 0           void pdfmake_text_combined_matrix(
122             pdfmake_text_state_t *ts,
123             pdfmake_render_ctx_t *ctx,
124             double out[6])
125             {
126             double ctm[6];
127 0 0         if (!ts || !out) return;
    0          
128            
129             /* Get CTM from render context */
130 0           ctm[0] = 1; ctm[1] = 0;
131 0           ctm[2] = 0; ctm[3] = 1;
132 0           ctm[4] = 0; ctm[5] = 0;
133 0 0         if (ctx) {
134 0           ctm[0] = ctx->ctm.a; ctm[1] = ctx->ctm.b;
135 0           ctm[2] = ctx->ctm.c; ctm[3] = ctx->ctm.d;
136 0           ctm[4] = ctx->ctm.e; ctm[5] = ctx->ctm.f;
137             }
138            
139             /* Combined = Tm * CTM */
140 0           out[0] = ts->tm[0] * ctm[0] + ts->tm[1] * ctm[2];
141 0           out[1] = ts->tm[0] * ctm[1] + ts->tm[1] * ctm[3];
142 0           out[2] = ts->tm[2] * ctm[0] + ts->tm[3] * ctm[2];
143 0           out[3] = ts->tm[2] * ctm[1] + ts->tm[3] * ctm[3];
144 0           out[4] = ts->tm[4] * ctm[0] + ts->tm[5] * ctm[2] + ctm[4];
145 0           out[5] = ts->tm[4] * ctm[1] + ts->tm[5] * ctm[3] + ctm[5];
146             }
147              
148             /*
149             * Build glyph rendering matrix.
150             * Transforms from glyph space (font units) to device space.
151             */
152 0           static void build_glyph_matrix(
153             pdfmake_text_state_t *ts,
154             pdfmake_render_ctx_t *ctx,
155             double out[6])
156             {
157             int units_per_em;
158             double scale;
159             double hscale;
160             double gm[6];
161             double tm_gm[6];
162             double ctm[6];
163 0 0         if (!ts || !out) return;
    0          
164            
165             /* Get units per em from font */
166 0           units_per_em = 1000;
167 0 0         if (ts->font) {
168 0 0         if (ts->font->type == PDFMAKE_FONT_TRUETYPE ||
169 0 0         ts->font->type == PDFMAKE_FONT_CID_TRUETYPE) {
170 0 0         if (ts->font->ttf) {
171 0           units_per_em = ts->font->ttf->units_per_em;
172             }
173             }
174             }
175            
176             /* Scale factor: font_size / units_per_em */
177 0           scale = ts->font_size / units_per_em;
178            
179             /* Apply horizontal scaling */
180 0           hscale = ts->horiz_scale;
181            
182             /* Glyph matrix = [scale*hscale 0 0 scale 0 rise] * Tm * CTM */
183 0           gm[0] = scale * hscale;
184 0           gm[1] = 0;
185 0           gm[2] = 0;
186 0           gm[3] = scale;
187 0           gm[4] = 0;
188 0           gm[5] = ts->text_rise;
189            
190             /* Multiply by text matrix */
191 0           tm_gm[0] = gm[0] * ts->tm[0] + gm[1] * ts->tm[2];
192 0           tm_gm[1] = gm[0] * ts->tm[1] + gm[1] * ts->tm[3];
193 0           tm_gm[2] = gm[2] * ts->tm[0] + gm[3] * ts->tm[2];
194 0           tm_gm[3] = gm[2] * ts->tm[1] + gm[3] * ts->tm[3];
195 0           tm_gm[4] = gm[4] * ts->tm[0] + gm[5] * ts->tm[2] + ts->tm[4];
196 0           tm_gm[5] = gm[4] * ts->tm[1] + gm[5] * ts->tm[3] + ts->tm[5];
197            
198             /* Multiply by CTM */
199 0           ctm[0] = 1; ctm[1] = 0;
200 0           ctm[2] = 0; ctm[3] = 1;
201 0           ctm[4] = 0; ctm[5] = 0;
202 0 0         if (ctx) {
203 0           ctm[0] = ctx->ctm.a; ctm[1] = ctx->ctm.b;
204 0           ctm[2] = ctx->ctm.c; ctm[3] = ctx->ctm.d;
205 0           ctm[4] = ctx->ctm.e; ctm[5] = ctx->ctm.f;
206             }
207              
208 0           out[0] = tm_gm[0] * ctm[0] + tm_gm[1] * ctm[2];
209 0           out[1] = tm_gm[0] * ctm[1] + tm_gm[1] * ctm[3];
210 0           out[2] = tm_gm[2] * ctm[0] + tm_gm[3] * ctm[2];
211 0           out[3] = tm_gm[2] * ctm[1] + tm_gm[3] * ctm[3];
212 0           out[4] = tm_gm[4] * ctm[0] + tm_gm[5] * ctm[2] + ctm[4];
213 0           out[5] = tm_gm[4] * ctm[1] + tm_gm[5] * ctm[3] + ctm[5];
214             }
215              
216             /*
217             * Update text position after rendering a glyph.
218             */
219 0           static void update_text_position(
220             pdfmake_text_state_t *ts,
221             double advance_width,
222             int is_space)
223             {
224             double tx;
225 0 0         if (!ts) return;
226            
227             /* Total displacement */
228 0           tx = advance_width;
229 0           tx += ts->char_spacing;
230            
231 0 0         if (is_space) {
232 0           tx += ts->word_spacing;
233             }
234            
235             /* Apply horizontal scaling */
236 0           tx *= ts->horiz_scale;
237            
238             /* Update text matrix: translate by tx in text space */
239 0           ts->tm[4] += tx * ts->tm[0];
240 0           ts->tm[5] += tx * ts->tm[1];
241             }
242              
243             /*============================================================================
244             * UTF-8 Decoding
245             *==========================================================================*/
246              
247 0           static uint32_t utf8_decode_char(const uint8_t **p, const uint8_t *end)
248             {
249             uint8_t c;
250             uint8_t c2, c3, c4;
251 0 0         if (*p >= end) return 0xFFFD;
252            
253 0           c = *(*p)++;
254            
255 0 0         if (c < 0x80) {
256 0           return c;
257             }
258            
259 0 0         if ((c & 0xE0) == 0xC0) {
260 0 0         if (*p >= end) return 0xFFFD;
261 0           c2 = *(*p)++;
262 0 0         if ((c2 & 0xC0) != 0x80) return 0xFFFD;
263 0           return ((c & 0x1F) << 6) | (c2 & 0x3F);
264             }
265            
266 0 0         if ((c & 0xF0) == 0xE0) {
267 0 0         if (*p + 1 >= end) return 0xFFFD;
268 0           c2 = *(*p)++;
269 0           c3 = *(*p)++;
270 0 0         if ((c2 & 0xC0) != 0x80 || (c3 & 0xC0) != 0x80) return 0xFFFD;
    0          
271 0           return ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
272             }
273            
274 0 0         if ((c & 0xF8) == 0xF0) {
275 0 0         if (*p + 2 >= end) return 0xFFFD;
276 0           c2 = *(*p)++;
277 0           c3 = *(*p)++;
278 0           c4 = *(*p)++;
279 0 0         if ((c2 & 0xC0) != 0x80 || (c3 & 0xC0) != 0x80 ||
    0          
280 0 0         (c4 & 0xC0) != 0x80) return 0xFFFD;
281 0           return ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) |
282 0           ((c3 & 0x3F) << 6) | (c4 & 0x3F);
283             }
284            
285 0           return 0xFFFD;
286             }
287              
288             /*============================================================================
289             * Glyph Rendering
290             *==========================================================================*/
291              
292 0           pdfmake_text_err_t pdfmake_render_glyph(
293             pdfmake_render_ctx_t *ctx,
294             pdfmake_text_state_t *ts,
295             uint16_t glyph_id)
296             {
297             pdfmake_glyph_outline_t *outline;
298             double advance;
299             double scaled_advance;
300             int units_per_em;
301 0 0         if (!ctx || !ts || !ts->font) return PDFMAKE_TEXT_ERR_NULL;
    0          
    0          
302 0 0         if (!ts->cache) return PDFMAKE_TEXT_ERR_INVALID_FONT;
303            
304             /* Get glyph outline */
305 0           outline = pdfmake_glyph_get(
306             ts->cache, ts->font, glyph_id);
307            
308 0 0         if (!outline) {
309             /* Unknown glyph - use .notdef (glyph 0) */
310 0           outline = pdfmake_glyph_get(ts->cache, ts->font, 0);
311             }
312            
313             /* Invisible mode - just update position */
314 0 0         if (ts->render_mode == PDFMAKE_TEXT_INVISIBLE) {
315 0           double inv_advance = pdfmake_text_glyph_advance(
316             ts->font, glyph_id, ts->font_size);
317 0           update_text_position(ts, inv_advance, 0);
318 0           return PDFMAKE_TEXT_OK;
319             }
320            
321             /* Render if we have an outline */
322 0 0         if (outline && outline->path && outline->path->seg_count > 0) {
    0          
    0          
323             /* Build transformation matrix */
324             double glyph_matrix[6];
325             pdfmake_arena_t *arena;
326             pdfmake_path_t *transformed;
327             int do_fill;
328             int do_stroke;
329             int do_clip;
330 0           build_glyph_matrix(ts, ctx, glyph_matrix);
331            
332             /* Transform glyph path */
333 0           arena = ts->cache->arena;
334 0           transformed = pdfmake_path_transform_copy(
335             outline->path, glyph_matrix, arena);
336            
337 0 0         if (transformed) {
338             /* Apply based on render mode */
339 0           do_fill = (ts->render_mode == PDFMAKE_TEXT_FILL ||
340 0 0         ts->render_mode == PDFMAKE_TEXT_FILL_STROKE ||
341 0 0         ts->render_mode == PDFMAKE_TEXT_FILL_CLIP ||
    0          
342 0 0         ts->render_mode == PDFMAKE_TEXT_FILL_STROKE_CLIP);
343            
344 0           do_stroke = (ts->render_mode == PDFMAKE_TEXT_STROKE ||
345 0 0         ts->render_mode == PDFMAKE_TEXT_FILL_STROKE ||
346 0 0         ts->render_mode == PDFMAKE_TEXT_STROKE_CLIP ||
    0          
347 0 0         ts->render_mode == PDFMAKE_TEXT_FILL_STROKE_CLIP);
348            
349 0           do_clip = (ts->render_mode == PDFMAKE_TEXT_FILL_CLIP ||
350 0 0         ts->render_mode == PDFMAKE_TEXT_STROKE_CLIP ||
351 0 0         ts->render_mode == PDFMAKE_TEXT_FILL_STROKE_CLIP ||
    0          
352 0 0         ts->render_mode == PDFMAKE_TEXT_CLIP);
353            
354             /* Fill */
355 0 0         if (do_fill) {
356 0           pdfmake_fill_path(ctx, transformed, PDFMAKE_FILL_NONZERO);
357             }
358            
359             /* Stroke */
360 0 0         if (do_stroke) {
361 0           pdfmake_stroke_path(ctx, transformed, NULL);
362             }
363            
364             /* Clip */
365 0 0         if (do_clip) {
366 0           pdfmake_clip_path(ctx, transformed, PDFMAKE_FILL_NONZERO);
367             }
368             }
369             }
370            
371             /* Update text position */
372 0 0         advance = outline ? outline->advance_width : 0;
373 0           scaled_advance = (advance * ts->font_size);
374            
375             /* Get units per em */
376 0           units_per_em = 1000;
377 0 0         if (ts->font->type == PDFMAKE_FONT_TRUETYPE ||
378 0 0         ts->font->type == PDFMAKE_FONT_CID_TRUETYPE) {
379 0 0         if (ts->font->ttf) {
380 0           units_per_em = ts->font->ttf->units_per_em;
381             }
382             }
383            
384 0           scaled_advance /= units_per_em;
385 0           update_text_position(ts, scaled_advance, 0);
386            
387 0           return PDFMAKE_TEXT_OK;
388             }
389              
390             /*============================================================================
391             * Text String Rendering
392             *==========================================================================*/
393              
394 0           pdfmake_text_err_t pdfmake_render_text(
395             pdfmake_render_ctx_t *ctx,
396             pdfmake_text_state_t *ts,
397             const uint8_t *text,
398             size_t len)
399             {
400             size_t i;
401 0 0         if (!ctx || !ts || !text) return PDFMAKE_TEXT_ERR_NULL;
    0          
    0          
402 0 0         if (!ts->font) return PDFMAKE_TEXT_ERR_INVALID_FONT;
403            
404             /* Process each byte as character code */
405 0 0         for (i = 0; i < len; i++) {
406 0           uint8_t charcode = text[i];
407            
408             /* Map char code to glyph */
409 0           uint16_t glyph_id = pdfmake_text_char_to_glyph(ts->font, charcode);
410            
411             /* Check for space */
412 0           int is_space = (charcode == 0x20);
413            
414             /* Render glyph */
415 0           pdfmake_text_err_t err = pdfmake_render_glyph(ctx, ts, glyph_id);
416 0 0         if (err != PDFMAKE_TEXT_OK) return err;
417            
418             /* Add word spacing for space character */
419 0 0         if (is_space) {
420 0           double ws = ts->word_spacing * ts->horiz_scale;
421 0           ts->tm[4] += ws * ts->tm[0];
422 0           ts->tm[5] += ws * ts->tm[1];
423             }
424             }
425            
426 0           return PDFMAKE_TEXT_OK;
427             }
428              
429 0           pdfmake_text_err_t pdfmake_render_text_utf8(
430             pdfmake_render_ctx_t *ctx,
431             pdfmake_text_state_t *ts,
432             const char *text,
433             size_t len)
434             {
435             const uint8_t *p;
436             const uint8_t *end;
437 0 0         if (!ctx || !ts || !text) return PDFMAKE_TEXT_ERR_NULL;
    0          
    0          
438 0 0         if (!ts->font) return PDFMAKE_TEXT_ERR_INVALID_FONT;
439            
440 0           p = (const uint8_t *)text;
441 0           end = p + len;
442            
443 0 0         while (p < end) {
444 0           uint32_t unicode = utf8_decode_char(&p, end);
445             uint16_t glyph_id;
446             int is_space;
447             pdfmake_text_err_t err;
448 0 0         if (unicode == 0xFFFD) continue;
449            
450             /* Map Unicode to glyph */
451 0           glyph_id = pdfmake_text_unicode_to_glyph(ts->font, unicode);
452            
453             /* Check for space */
454 0           is_space = (unicode == 0x0020);
455            
456             /* Render glyph */
457 0           err = pdfmake_render_glyph(ctx, ts, glyph_id);
458 0 0         if (err != PDFMAKE_TEXT_OK) return err;
459            
460             /* Add word spacing for space character */
461 0 0         if (is_space) {
462 0           double ws = ts->word_spacing * ts->horiz_scale;
463 0           ts->tm[4] += ws * ts->tm[0];
464 0           ts->tm[5] += ws * ts->tm[1];
465             }
466             }
467            
468 0           return PDFMAKE_TEXT_OK;
469             }
470              
471 0           pdfmake_text_err_t pdfmake_render_text_positioned(
472             pdfmake_render_ctx_t *ctx,
473             pdfmake_text_state_t *ts,
474             const pdfmake_text_element_t *elements,
475             size_t count)
476             {
477             size_t i;
478 0 0         if (!ctx || !ts || !elements) return PDFMAKE_TEXT_ERR_NULL;
    0          
    0          
479 0 0         if (!ts->font) return PDFMAKE_TEXT_ERR_INVALID_FONT;
480            
481 0 0         for (i = 0; i < count; i++) {
482 0           const pdfmake_text_element_t *elem = &elements[i];
483            
484 0 0         if (elem->type == PDFMAKE_TEXT_ELEM_STRING) {
485             /* Render text string */
486 0           pdfmake_text_err_t err = pdfmake_render_text(
487 0           ctx, ts, elem->u.string.data, elem->u.string.len);
488 0 0         if (err != PDFMAKE_TEXT_OK) return err;
489 0 0         } else if (elem->type == PDFMAKE_TEXT_ELEM_ADJUST) {
490             /* Position adjustment */
491             /* Negative value moves right, positive moves left */
492             /* Value is in 1/1000 of text space units */
493 0           double adjust = -elem->u.adjust / 1000.0 * ts->font_size;
494 0           adjust *= ts->horiz_scale;
495            
496 0           ts->tm[4] += adjust * ts->tm[0];
497 0           ts->tm[5] += adjust * ts->tm[1];
498             }
499             }
500            
501 0           return PDFMAKE_TEXT_OK;
502             }
503              
504             /*============================================================================
505             * Convenience Functions
506             *==========================================================================*/
507              
508             /*
509             * Render text at a specific position.
510             * Sets text matrix and renders.
511             */
512 0           pdfmake_text_err_t pdfmake_render_text_at(
513             pdfmake_render_ctx_t *ctx,
514             pdfmake_text_state_t *ts,
515             double x, double y,
516             const char *text,
517             size_t len)
518             {
519 0 0         if (!ctx || !ts || !text) return PDFMAKE_TEXT_ERR_NULL;
    0          
    0          
520            
521             /* Set text matrix to position */
522 0           pdfmake_text_set_matrix(ts, 1, 0, 0, 1, x, y);
523            
524             /* Render UTF-8 text */
525 0           return pdfmake_render_text_utf8(ctx, ts, text, len);
526             }
527              
528             /*
529             * Get text bounding box (approximate).
530             * Returns the width and uses font metrics for height.
531             */
532 0           void pdfmake_text_bbox(
533             pdfmake_text_state_t *ts,
534             const char *text,
535             size_t len,
536             double *out_width,
537             double *out_height,
538             double *out_descent)
539             {
540             double width;
541             const pdfmake_font_metrics_t *metrics;
542 0 0         if (!ts || !ts->font || !text) {
    0          
    0          
543 0 0         if (out_width) *out_width = 0;
544 0 0         if (out_height) *out_height = 0;
545 0 0         if (out_descent) *out_descent = 0;
546 0           return;
547             }
548            
549             /* Calculate width */
550 0           width = pdfmake_text_string_width(ts, (const uint8_t *)text, len);
551 0 0         if (out_width) *out_width = width;
552            
553             /* Get font metrics */
554 0           metrics = pdfmake_font_metrics(ts->font);
555            
556 0 0         if (metrics) {
557 0           double scale = ts->font_size / 1000.0;
558            
559 0 0         if (out_height) {
560 0           *out_height = (metrics->ascent - metrics->descent) * scale;
561             }
562 0 0         if (out_descent) {
563 0           *out_descent = metrics->descent * scale; /* Negative value */
564             }
565             } else {
566             /* Fallback estimates */
567 0 0         if (out_height) *out_height = ts->font_size;
568 0 0         if (out_descent) *out_descent = -ts->font_size * 0.2;
569             }
570             }