File Coverage

src/pdfmake_watermark.c
Criterion Covered Total %
statement 303 481 62.9
branch 108 238 45.3
condition n/a
subroutine n/a
pod n/a
total 411 719 57.1


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_watermark.c — Watermarks and stamps implementation.
3             *
4             * Implements watermark and stamp functionality for PDF pages.
5             */
6              
7             #include "pdfmake_watermark.h"
8             #include "pdfmake_content.h"
9             #include "pdfmake_buf.h"
10             #include "pdfmake_arena.h"
11             #include
12             #include
13             #include
14             #include
15             #include
16              
17             #ifndef M_PI
18             #define M_PI 3.14159265358979323846
19             #endif
20              
21             /* Helper for appending strings to buffer */
22 27           static void pdfmake_buf_append_str(pdfmake_buf_t *buf, const char *s)
23             {
24 27           pdfmake_buf_append_cstr(buf, s);
25 27           }
26              
27             /*----------------------------------------------------------------------------
28             * Approximate character widths for Standard 14 fonts (simplified)
29             * Real implementation would use AFM data
30             *--------------------------------------------------------------------------*/
31              
32             /* Average character width as fraction of font size for common fonts */
33 27           static double get_char_width_factor(const char *font_name)
34             {
35 27 50         if (!font_name) return 0.5;
36            
37             /* Courier is fixed-width */
38 27 50         if (strstr(font_name, "Courier")) {
39 0           return 0.6; /* Fixed width */
40             }
41            
42             /* Helvetica/Arial style */
43 27 100         if (strstr(font_name, "Helvetica") || strstr(font_name, "Arial")) {
    50          
44 26           return 0.52;
45             }
46            
47             /* Times/serif style */
48 1 50         if (strstr(font_name, "Times")) {
49 1           return 0.45;
50             }
51            
52             /* Default */
53 0           return 0.5;
54             }
55              
56             /*----------------------------------------------------------------------------
57             * Default options initialization
58             *--------------------------------------------------------------------------*/
59              
60 28           void pdfmake_watermark_opts_init(pdfmake_watermark_opts_t *opts)
61             {
62 28 50         if (!opts) return;
63            
64 28           memset(opts, 0, sizeof(*opts));
65 28           opts->position = PDFMAKE_WM_POS_CENTER;
66 28           opts->rotation = 0.0;
67 28           opts->opacity = 0.3;
68 28           opts->scale = 1.0;
69 28           opts->x_offset = 0.0;
70 28           opts->y_offset = 0.0;
71 28           opts->as_overlay = 0; /* Behind content by default */
72            
73 28           opts->font_name = "Helvetica-Bold";
74 28           opts->font_size = 72.0;
75 28           opts->color[0] = 0.7; /* Light gray */
76 28           opts->color[1] = 0.7;
77 28           opts->color[2] = 0.7;
78            
79 28           opts->tile_spacing_x = 150.0;
80 28           opts->tile_spacing_y = 150.0;
81             }
82              
83 10           void pdfmake_stamp_opts_init(pdfmake_stamp_opts_t *opts)
84             {
85 10 50         if (!opts) return;
86            
87 10           memset(opts, 0, sizeof(*opts));
88 10           opts->position = PDFMAKE_WM_POS_BOTTOM_CENTER;
89 10           opts->margin_x = 36.0; /* Half inch */
90 10           opts->margin_y = 36.0;
91 10           opts->font_name = "Helvetica";
92 10           opts->font_size = 10.0;
93 10           opts->color[0] = 0.0; /* Black */
94 10           opts->color[1] = 0.0;
95 10           opts->color[2] = 0.0;
96             }
97              
98             /*----------------------------------------------------------------------------
99             * Text metrics
100             *--------------------------------------------------------------------------*/
101              
102 27           double pdfmake_text_width_approx(const char *text, const char *font_name,
103             double font_size)
104             {
105             double factor;
106             size_t len;
107              
108 27 50         if (!text) return 0.0;
109            
110 27           factor = get_char_width_factor(font_name);
111 27           len = strlen(text);
112            
113 27           return len * font_size * factor;
114             }
115              
116             /*----------------------------------------------------------------------------
117             * Watermark creation
118             *--------------------------------------------------------------------------*/
119              
120 24           pdfmake_watermark_t *pdfmake_watermark_text(pdfmake_doc_t *doc,
121             const char *text,
122             const pdfmake_watermark_opts_t *opts)
123             {
124             pdfmake_watermark_t *wm;
125              
126             (void)doc;
127 24 50         if (!text) return NULL;
128            
129 24           wm = calloc(1, sizeof(pdfmake_watermark_t));
130 24 50         if (!wm) return NULL;
131            
132 24           wm->type = PDFMAKE_WM_TYPE_TEXT;
133            
134             /* Copy options */
135 24 50         if (opts) {
136 24           wm->opts = *opts;
137             } else {
138 0           pdfmake_watermark_opts_init(&wm->opts);
139             }
140            
141             /* Copy text */
142 24           wm->data.text.text = strdup(text);
143 24 50         if (!wm->data.text.text) {
144 0           free(wm);
145 0           return NULL;
146             }
147            
148             /* Calculate text dimensions */
149 24           wm->data.text.text_width = pdfmake_text_width_approx(
150             text, wm->opts.font_name, wm->opts.font_size);
151 24           wm->data.text.text_height = wm->opts.font_size;
152            
153 24           return wm;
154             }
155              
156 1           pdfmake_watermark_t *pdfmake_watermark_image(pdfmake_doc_t *doc,
157             uint32_t image_obj_num,
158             double image_width,
159             double image_height,
160             const pdfmake_watermark_opts_t *opts)
161             {
162             pdfmake_watermark_t *wm;
163              
164             (void)doc;
165 1 50         if (image_obj_num == 0) return NULL;
166            
167 1           wm = calloc(1, sizeof(pdfmake_watermark_t));
168 1 50         if (!wm) return NULL;
169            
170 1           wm->type = PDFMAKE_WM_TYPE_IMAGE;
171            
172             /* Copy options */
173 1 50         if (opts) {
174 1           wm->opts = *opts;
175             } else {
176 0           pdfmake_watermark_opts_init(&wm->opts);
177             }
178            
179 1           wm->data.image.image_obj = image_obj_num;
180 1           wm->data.image.width = image_width;
181 1           wm->data.image.height = image_height;
182            
183 1           return wm;
184             }
185              
186 25           void pdfmake_watermark_free(pdfmake_watermark_t *wm)
187             {
188 25 50         if (!wm) return;
189            
190 25 100         if (wm->type == PDFMAKE_WM_TYPE_TEXT && wm->data.text.text) {
    50          
191 24           free(wm->data.text.text);
192             }
193            
194 25           free(wm);
195             }
196              
197             /*----------------------------------------------------------------------------
198             * Stamp creation
199             *--------------------------------------------------------------------------*/
200              
201 6           pdfmake_stamp_t *pdfmake_stamp_text(pdfmake_doc_t *doc,
202             const char *format,
203             const pdfmake_stamp_opts_t *opts)
204             {
205             pdfmake_stamp_t *stamp;
206              
207             (void)doc;
208 6 50         if (!format) return NULL;
209            
210 6           stamp = calloc(1, sizeof(pdfmake_stamp_t));
211 6 50         if (!stamp) return NULL;
212            
213 6           stamp->type = PDFMAKE_WM_STAMP_TEXT;
214            
215             /* Copy options */
216 6 50         if (opts) {
217 6           stamp->opts = *opts;
218             } else {
219 0           pdfmake_stamp_opts_init(&stamp->opts);
220             }
221            
222             /* Copy format string */
223 6           stamp->data.text.format = strdup(format);
224 6 50         if (!stamp->data.text.format) {
225 0           free(stamp);
226 0           return NULL;
227             }
228            
229 6           return stamp;
230             }
231              
232 3           pdfmake_stamp_t *pdfmake_stamp_bates(pdfmake_doc_t *doc,
233             const char *prefix,
234             int start_number,
235             int digits,
236             const char *suffix,
237             const pdfmake_stamp_opts_t *opts)
238             {
239             pdfmake_stamp_t *stamp;
240              
241             (void)doc;
242            
243 3           stamp = calloc(1, sizeof(pdfmake_stamp_t));
244 3 50         if (!stamp) return NULL;
245            
246 3           stamp->type = PDFMAKE_WM_STAMP_BATES;
247            
248             /* Copy options */
249 3 50         if (opts) {
250 3           stamp->opts = *opts;
251             } else {
252 0           pdfmake_stamp_opts_init(&stamp->opts);
253             }
254            
255             /* Copy strings */
256 3 50         stamp->data.bates.prefix = prefix ? strdup(prefix) : strdup("");
257 3 50         stamp->data.bates.suffix = suffix ? strdup(suffix) : strdup("");
258 3           stamp->data.bates.start_number = start_number;
259 3 50         stamp->data.bates.digits = digits > 0 ? digits : 6;
260 3           stamp->data.bates.current_number = start_number;
261            
262 3 50         if (!stamp->data.bates.prefix || !stamp->data.bates.suffix) {
    50          
263 0           free(stamp->data.bates.prefix);
264 0           free(stamp->data.bates.suffix);
265 0           free(stamp);
266 0           return NULL;
267             }
268            
269 3           return stamp;
270             }
271              
272 9           void pdfmake_stamp_free(pdfmake_stamp_t *stamp)
273             {
274 9 50         if (!stamp) return;
275            
276 9 100         if (stamp->type == PDFMAKE_WM_STAMP_TEXT && stamp->data.text.format) {
    50          
277 6           free(stamp->data.text.format);
278 3 50         } else if (stamp->type == PDFMAKE_WM_STAMP_BATES) {
279 3           free(stamp->data.bates.prefix);
280 3           free(stamp->data.bates.suffix);
281             }
282            
283 9           free(stamp);
284             }
285              
286             /*----------------------------------------------------------------------------
287             * Position calculations
288             *--------------------------------------------------------------------------*/
289              
290 3           void pdfmake_watermark_calc_position(const pdfmake_watermark_t *wm,
291             double page_width,
292             double page_height,
293             double *out_x,
294             double *out_y,
295             double *out_rotation)
296             {
297             double content_width, content_height;
298              
299 3 50         if (!wm || !out_x || !out_y || !out_rotation) return;
    50          
    50          
    50          
300            
301 3 50         if (wm->type == PDFMAKE_WM_TYPE_TEXT) {
302 3           content_width = wm->data.text.text_width * wm->opts.scale;
303 3           content_height = wm->data.text.text_height * wm->opts.scale;
304             } else {
305 0           content_width = wm->data.image.width * wm->opts.scale;
306 0           content_height = wm->data.image.height * wm->opts.scale;
307             }
308            
309 3           *out_rotation = wm->opts.rotation;
310            
311 3           switch (wm->opts.position) {
312 3           case PDFMAKE_WM_POS_CENTER:
313 3           *out_x = (page_width - content_width) / 2.0;
314 3           *out_y = (page_height - content_height) / 2.0;
315 3           break;
316            
317 0           case PDFMAKE_WM_POS_DIAGONAL:
318             /* Calculate angle to span corner to corner */
319 0           *out_rotation = atan2(page_height, page_width) * 180.0 / M_PI;
320             /* Position at center */
321 0           *out_x = page_width / 2.0;
322 0           *out_y = page_height / 2.0;
323 0           break;
324            
325 0           case PDFMAKE_WM_POS_TOP_LEFT:
326 0           *out_x = wm->opts.x_offset;
327 0           *out_y = page_height - content_height - wm->opts.y_offset;
328 0           break;
329            
330 0           case PDFMAKE_WM_POS_TOP_CENTER:
331 0           *out_x = (page_width - content_width) / 2.0;
332 0           *out_y = page_height - content_height - wm->opts.y_offset;
333 0           break;
334            
335 0           case PDFMAKE_WM_POS_TOP_RIGHT:
336 0           *out_x = page_width - content_width - wm->opts.x_offset;
337 0           *out_y = page_height - content_height - wm->opts.y_offset;
338 0           break;
339            
340 0           case PDFMAKE_WM_POS_BOTTOM_LEFT:
341 0           *out_x = wm->opts.x_offset;
342 0           *out_y = wm->opts.y_offset;
343 0           break;
344            
345 0           case PDFMAKE_WM_POS_BOTTOM_CENTER:
346 0           *out_x = (page_width - content_width) / 2.0;
347 0           *out_y = wm->opts.y_offset;
348 0           break;
349            
350 0           case PDFMAKE_WM_POS_BOTTOM_RIGHT:
351 0           *out_x = page_width - content_width - wm->opts.x_offset;
352 0           *out_y = wm->opts.y_offset;
353 0           break;
354            
355 0           case PDFMAKE_WM_POS_LEFT_CENTER:
356 0           *out_x = wm->opts.x_offset;
357 0           *out_y = (page_height - content_height) / 2.0;
358 0           break;
359            
360 0           case PDFMAKE_WM_POS_RIGHT_CENTER:
361 0           *out_x = page_width - content_width - wm->opts.x_offset;
362 0           *out_y = (page_height - content_height) / 2.0;
363 0           break;
364            
365 0           case PDFMAKE_WM_POS_TILE:
366             case PDFMAKE_WM_POS_CUSTOM:
367             default:
368 0           *out_x = wm->opts.x_offset;
369 0           *out_y = wm->opts.y_offset;
370 0           break;
371             }
372            
373             /* Apply offsets */
374 3           *out_x += wm->opts.x_offset;
375 3           *out_y += wm->opts.y_offset;
376             }
377              
378 3           void pdfmake_stamp_calc_position(const pdfmake_stamp_t *stamp,
379             double page_width,
380             double page_height,
381             double text_width,
382             double text_height,
383             double *out_x,
384             double *out_y)
385             {
386             double margin_x, margin_y;
387              
388 3 50         if (!stamp || !out_x || !out_y) return;
    50          
    50          
389            
390 3           margin_x = stamp->opts.margin_x;
391 3           margin_y = stamp->opts.margin_y;
392            
393 3           switch (stamp->opts.position) {
394 0           case PDFMAKE_WM_POS_TOP_LEFT:
395 0           *out_x = margin_x;
396 0           *out_y = page_height - margin_y - text_height;
397 0           break;
398            
399 0           case PDFMAKE_WM_POS_TOP_CENTER:
400 0           *out_x = (page_width - text_width) / 2.0;
401 0           *out_y = page_height - margin_y - text_height;
402 0           break;
403            
404 0           case PDFMAKE_WM_POS_TOP_RIGHT:
405 0           *out_x = page_width - margin_x - text_width;
406 0           *out_y = page_height - margin_y - text_height;
407 0           break;
408            
409 0           case PDFMAKE_WM_POS_BOTTOM_LEFT:
410 0           *out_x = margin_x;
411 0           *out_y = margin_y;
412 0           break;
413            
414 2           case PDFMAKE_WM_POS_BOTTOM_CENTER:
415 2           *out_x = (page_width - text_width) / 2.0;
416 2           *out_y = margin_y;
417 2           break;
418            
419 1           case PDFMAKE_WM_POS_BOTTOM_RIGHT:
420 1           *out_x = page_width - margin_x - text_width;
421 1           *out_y = margin_y;
422 1           break;
423            
424 0           case PDFMAKE_WM_POS_CENTER:
425 0           *out_x = (page_width - text_width) / 2.0;
426 0           *out_y = (page_height - text_height) / 2.0;
427 0           break;
428            
429 0           case PDFMAKE_WM_POS_LEFT_CENTER:
430 0           *out_x = margin_x;
431 0           *out_y = (page_height - text_height) / 2.0;
432 0           break;
433            
434 0           case PDFMAKE_WM_POS_RIGHT_CENTER:
435 0           *out_x = page_width - margin_x - text_width;
436 0           *out_y = (page_height - text_height) / 2.0;
437 0           break;
438            
439 0           default:
440 0           *out_x = margin_x;
441 0           *out_y = margin_y;
442 0           break;
443             }
444             }
445              
446             /*----------------------------------------------------------------------------
447             * Format string expansion
448             *--------------------------------------------------------------------------*/
449              
450 4           char *pdfmake_stamp_expand_format(const char *format,
451             int page_number,
452             int total_pages,
453             const char *filename)
454             {
455             size_t out_size;
456             char *output;
457             char *dst;
458             const char *src;
459             char temp[64];
460             time_t now;
461             struct tm *tm_info;
462              
463 4 50         if (!format) return NULL;
464            
465             /* Calculate output size (generous estimate) */
466 4           out_size = strlen(format) * 2 + 256;
467 4           output = malloc(out_size);
468 4 50         if (!output) return NULL;
469            
470 4           dst = output;
471 4           src = format;
472            
473 4           now = time(NULL);
474 4           tm_info = localtime(&now);
475            
476 50 100         while (*src && (size_t)(dst - output) < out_size - 64) {
    50          
477 46 100         if (*src == '%' && *(src + 1)) {
    50          
478 13           src++;
479 13           switch (*src) {
480 4           case 'p': /* Current page number */
481 4           snprintf(temp, sizeof(temp), "%d", page_number);
482 4           strcpy(dst, temp);
483 4           dst += strlen(temp);
484 4           break;
485            
486 2           case 'P': /* Total pages */
487 2           snprintf(temp, sizeof(temp), "%d", total_pages);
488 2           strcpy(dst, temp);
489 2           dst += strlen(temp);
490 2           break;
491            
492 2           case 'd': /* Date YYYY-MM-DD */
493 2           strftime(temp, sizeof(temp), "%Y-%m-%d", tm_info);
494 2           strcpy(dst, temp);
495 2           dst += strlen(temp);
496 2           break;
497            
498 1           case 't': /* Time HH:MM */
499 1           strftime(temp, sizeof(temp), "%H:%M", tm_info);
500 1           strcpy(dst, temp);
501 1           dst += strlen(temp);
502 1           break;
503            
504 0           case 'f': /* Filename */
505 0 0         if (filename) {
506 0           strcpy(dst, filename);
507 0           dst += strlen(filename);
508             }
509 0           break;
510            
511 4           case '%': /* Literal % */
512 4           *dst++ = '%';
513 4           break;
514            
515 0           default:
516             /* Unknown specifier, keep as-is */
517 0           *dst++ = '%';
518 0           *dst++ = *src;
519 0           break;
520             }
521 13           src++;
522             } else {
523 33           *dst++ = *src++;
524             }
525             }
526            
527 4           *dst = '\0';
528 4           return output;
529             }
530              
531 5           char *pdfmake_stamp_expand_bates(const char *prefix,
532             int number,
533             int digits,
534             const char *suffix)
535             {
536 5 50         size_t prefix_len = prefix ? strlen(prefix) : 0;
537 5 50         size_t suffix_len = suffix ? strlen(suffix) : 0;
538             char *output;
539             char format[32];
540            
541 5           output = malloc(prefix_len + digits + suffix_len + 16);
542 5 50         if (!output) return NULL;
543            
544 5           snprintf(format, sizeof(format), "%%s%%0%dd%%s", digits);
545 5 50         snprintf(output, prefix_len + digits + suffix_len + 16, format,
    50          
546             prefix ? prefix : "", number, suffix ? suffix : "");
547            
548 5           return output;
549             }
550              
551             /*----------------------------------------------------------------------------
552             * ExtGState for opacity
553             *--------------------------------------------------------------------------*/
554              
555             /*----------------------------------------------------------------------------
556             * ExtGState for opacity
557             *--------------------------------------------------------------------------*/
558              
559 2           uint32_t pdfmake_doc_get_opacity_extgstate(pdfmake_doc_t *doc, double opacity)
560             {
561             pdfmake_arena_t *arena;
562             pdfmake_obj_t gs;
563             uint32_t type_k, ca_k, CA_k;
564              
565 2 50         if (!doc) return 0;
566            
567             /* Clamp opacity */
568 2 50         if (opacity < 0.0) opacity = 0.0;
569 2 50         if (opacity > 1.0) opacity = 1.0;
570            
571 2           arena = pdfmake_doc_arena(doc);
572 2 50         if (!arena) return 0;
573              
574 2           gs = pdfmake_dict_new(arena);
575 2 50         if (gs.kind != PDFMAKE_DICT) return 0;
576              
577 2           type_k = pdfmake_arena_intern_name(arena, "Type", 4);
578 2           ca_k = pdfmake_arena_intern_name(arena, "ca", 2);
579 2           CA_k = pdfmake_arena_intern_name(arena, "CA", 2);
580              
581 2           pdfmake_dict_set(arena, &gs, type_k, pdfmake_name_cstr(arena, "ExtGState"));
582 2           pdfmake_dict_set(arena, &gs, ca_k, pdfmake_real(opacity));
583 2           pdfmake_dict_set(arena, &gs, CA_k, pdfmake_real(opacity));
584              
585 2           return pdfmake_doc_add(doc, gs);
586             }
587              
588             /*----------------------------------------------------------------------------
589             * Content generation helpers
590             *--------------------------------------------------------------------------*/
591              
592             /* Escape special characters in PDF string */
593 6           static void escape_pdf_string(pdfmake_buf_t *buf, const char *text)
594             {
595             const char *p;
596              
597 6           pdfmake_buf_append_byte(buf, '(');
598            
599 41 100         for (p = text; *p; p++) {
600 35 50         switch (*p) {
601 0           case '(':
602             case ')':
603             case '\\':
604 0           pdfmake_buf_append_byte(buf, '\\');
605 0           pdfmake_buf_append_byte(buf, *p);
606 0           break;
607 35           default:
608 35           pdfmake_buf_append_byte(buf, *p);
609 35           break;
610             }
611             }
612            
613 6           pdfmake_buf_append_byte(buf, ')');
614 6           }
615              
616             /* Generate watermark content stream */
617 3           static pdfmake_err_t generate_watermark_content(pdfmake_watermark_t *wm,
618             pdfmake_page_t *page,
619             pdfmake_buf_t *buf,
620             const char *gs_name,
621             const char *font_name,
622             const char *img_name)
623             {
624             double x, y, rotation;
625             double draw_r, draw_g, draw_b;
626             double spacing_x, spacing_y;
627             double content_width, content_height;
628             double ty, tx;
629             char pos[128];
630             char transform[256];
631             double rad, cos_r, sin_r;
632             double diag_content_width;
633             char text_ops[256];
634             double w, h;
635             char img_ops[128];
636              
637 3           pdfmake_watermark_calc_position(wm, page->width, page->height,
638             &x, &y, &rotation);
639              
640 3           draw_r = wm->opts.color[0];
641 3           draw_g = wm->opts.color[1];
642 3           draw_b = wm->opts.color[2];
643            
644             /* Save graphics state */
645 3           pdfmake_buf_append_str(buf, "q\n");
646            
647             /* Set opacity via ExtGState */
648 3 50         if (gs_name) {
649 3           pdfmake_buf_append_str(buf, "/");
650 3           pdfmake_buf_append_str(buf, gs_name);
651 3           pdfmake_buf_append_str(buf, " gs\n");
652             }
653            
654 3 50         if (wm->opts.position == PDFMAKE_WM_POS_TILE) {
655             /* Tile watermark across page */
656 0           spacing_x = wm->opts.tile_spacing_x;
657 0           spacing_y = wm->opts.tile_spacing_y;
658            
659 0           content_width = (wm->type == PDFMAKE_WM_TYPE_TEXT) ?
660 0 0         wm->data.text.text_width : wm->data.image.width;
661 0           content_height = (wm->type == PDFMAKE_WM_TYPE_TEXT) ?
662 0 0         wm->data.text.text_height : wm->data.image.height;
663            
664 0           content_width *= wm->opts.scale;
665 0           content_height *= wm->opts.scale;
666            
667 0 0         for (ty = 0; ty < page->height + content_height; ty += spacing_y) {
668 0 0         for (tx = 0; tx < page->width + content_width; tx += spacing_x) {
669             /* Position for this tile */
670 0 0         if (wm->type == PDFMAKE_WM_TYPE_TEXT) {
671             /* Text watermark */
672 0           snprintf(pos, sizeof(pos),
673             "BT\n"
674             "/%.50s %.2f Tf\n"
675             "%.3f %.3f %.3f rg\n"
676             "%.2f %.2f Td\n",
677 0           font_name, wm->opts.font_size * wm->opts.scale,
678             draw_r, draw_g, draw_b,
679             tx, ty);
680 0           pdfmake_buf_append_str(buf, pos);
681 0           escape_pdf_string(buf, wm->data.text.text);
682 0           pdfmake_buf_append_str(buf, " Tj\nET\n");
683             } else {
684             /* Image watermark */
685 0           snprintf(pos, sizeof(pos),
686             "q\n"
687             "%.2f 0 0 %.2f %.2f %.2f cm\n"
688             "/%.50s Do\n"
689             "Q\n",
690             content_width, content_height, tx, ty,
691             img_name);
692 0           pdfmake_buf_append_str(buf, pos);
693             }
694             }
695             }
696             } else {
697             /* Single watermark */
698 3 50         if (wm->opts.position == PDFMAKE_WM_POS_DIAGONAL) {
699             /* For diagonal: translate to center, rotate, translate back */
700 0           rad = rotation * M_PI / 180.0;
701 0           cos_r = cos(rad);
702 0           sin_r = sin(rad);
703            
704 0           snprintf(transform, sizeof(transform),
705             "1 0 0 1 %.2f %.2f cm\n"
706             "%.4f %.4f %.4f %.4f 0 0 cm\n",
707             x, y, cos_r, sin_r, -sin_r, cos_r);
708 0           pdfmake_buf_append_str(buf, transform);
709            
710             /* Offset to center text */
711 0           diag_content_width = (wm->type == PDFMAKE_WM_TYPE_TEXT) ?
712 0 0         wm->data.text.text_width * wm->opts.scale :
713 0           wm->data.image.width * wm->opts.scale;
714            
715 0           x = -diag_content_width / 2.0;
716 0           y = 0;
717 3 50         } else if (rotation != 0.0) {
718 0           rad = rotation * M_PI / 180.0;
719 0           cos_r = cos(rad);
720 0           sin_r = sin(rad);
721            
722 0           snprintf(transform, sizeof(transform),
723             "1 0 0 1 %.2f %.2f cm\n"
724             "%.4f %.4f %.4f %.4f 0 0 cm\n",
725             x, y, cos_r, sin_r, -sin_r, cos_r);
726 0           pdfmake_buf_append_str(buf, transform);
727 0           x = 0;
728 0           y = 0;
729             }
730            
731 3 50         if (wm->type == PDFMAKE_WM_TYPE_TEXT) {
732             /* Text watermark */
733 3           snprintf(text_ops, sizeof(text_ops),
734             "BT\n"
735             "/%.50s %.2f Tf\n"
736             "%.3f %.3f %.3f rg\n"
737             "%.2f %.2f Td\n",
738 3           font_name, wm->opts.font_size * wm->opts.scale,
739             draw_r, draw_g, draw_b,
740             x, y);
741 3           pdfmake_buf_append_str(buf, text_ops);
742 3           escape_pdf_string(buf, wm->data.text.text);
743 3           pdfmake_buf_append_str(buf, " Tj\nET\n");
744             } else {
745             /* Image watermark */
746 0           w = wm->data.image.width * wm->opts.scale;
747 0           h = wm->data.image.height * wm->opts.scale;
748            
749 0           snprintf(img_ops, sizeof(img_ops),
750             "q\n"
751             "%.2f 0 0 %.2f %.2f %.2f cm\n"
752             "/%.50s Do\n"
753             "Q\n",
754             w, h, x, y, img_name);
755 0           pdfmake_buf_append_str(buf, img_ops);
756             }
757             }
758            
759             /* Restore graphics state */
760 3           pdfmake_buf_append_str(buf, "Q\n");
761            
762 3           return PDFMAKE_OK;
763             }
764              
765             /* Generate stamp content stream */
766 3           static pdfmake_err_t generate_stamp_content(pdfmake_stamp_t *stamp,
767             pdfmake_page_t *page,
768             int page_num,
769             int total_pages,
770             pdfmake_buf_t *buf,
771             const char *font_name)
772             {
773 3           char *text = NULL;
774             double text_width, text_height;
775             double x, y;
776             char ops[512];
777              
778 3 100         if (stamp->type == PDFMAKE_WM_STAMP_TEXT) {
779 2           text = pdfmake_stamp_expand_format(stamp->data.text.format,
780             page_num, total_pages, NULL);
781             } else {
782 1           text = pdfmake_stamp_expand_bates(stamp->data.bates.prefix,
783             stamp->data.bates.current_number,
784             stamp->data.bates.digits,
785 1           stamp->data.bates.suffix);
786 1           stamp->data.bates.current_number++;
787             }
788            
789 3 50         if (!text) return PDFMAKE_ENOMEM;
790            
791 3           text_width = pdfmake_text_width_approx(text, stamp->opts.font_name,
792             stamp->opts.font_size);
793 3           text_height = stamp->opts.font_size;
794            
795 3           pdfmake_stamp_calc_position(stamp, page->width, page->height,
796             text_width, text_height, &x, &y);
797            
798             /* Save state, draw text, restore */
799 3           snprintf(ops, sizeof(ops),
800             "q\n"
801             "BT\n"
802             "/%.50s %.2f Tf\n"
803             "%.3f %.3f %.3f rg\n"
804             "%.2f %.2f Td\n",
805             font_name, stamp->opts.font_size,
806             stamp->opts.color[0], stamp->opts.color[1], stamp->opts.color[2],
807             x, y);
808 3           pdfmake_buf_append_str(buf, ops);
809 3           escape_pdf_string(buf, text);
810 3           pdfmake_buf_append_str(buf, " Tj\nET\nQ\n");
811            
812 3           free(text);
813 3           return PDFMAKE_OK;
814             }
815              
816             /*----------------------------------------------------------------------------
817             * Watermark application
818             *--------------------------------------------------------------------------*/
819              
820 3           pdfmake_err_t pdfmake_page_add_watermark(pdfmake_page_t *page,
821             pdfmake_watermark_t *wm)
822             {
823             pdfmake_doc_t *doc;
824             char gs_name[32];
825             char font_res_name[32];
826             char img_name[32];
827             const char *base_font;
828             pdfmake_buf_t buf;
829             const uint8_t *wm_data;
830             size_t wm_len;
831 3           const uint8_t *old_data = NULL;
832 3           size_t old_len = 0;
833             pdfmake_obj_t *old_obj;
834             pdfmake_buf_t merged;
835              
836 3 50         if (!page || !wm) return PDFMAKE_EINVAL;
    50          
837              
838             /* Fully transparent watermark: no-op. */
839 3 50         if (wm->opts.opacity <= 0.0) {
840 0           return PDFMAKE_OK;
841             }
842            
843 3           doc = page->doc;
844            
845 3           gs_name[0] = '\0';
846 3 50         if (wm->opts.opacity < 1.0) {
847 3 100         if (wm->extgstate_num == 0) {
848 2           wm->extgstate_num = pdfmake_doc_get_opacity_extgstate(doc, wm->opts.opacity);
849             }
850 3 50         if (wm->extgstate_num) {
851 3           snprintf(gs_name, sizeof(gs_name), "GS%u", wm->extgstate_num);
852 3           pdfmake_page_add_extgstate(page, gs_name, wm->extgstate_num);
853             }
854             }
855            
856             /* Font name for text watermarks */
857 3           strcpy(font_res_name, "F1");
858 3 50         if (wm->type == PDFMAKE_WM_TYPE_TEXT) {
859             /* Add font to page resources if not present */
860 3 50         base_font = wm->opts.font_name ? wm->opts.font_name : "Helvetica";
861 3           pdfmake_page_add_font(page, font_res_name, base_font);
862             }
863            
864             /* Image name for image watermarks */
865 3           strcpy(img_name, "WmImg0");
866 3 50         if (wm->type == PDFMAKE_WM_TYPE_IMAGE) {
867 0           snprintf(img_name, sizeof(img_name), "WmImg%u", wm->data.image.image_obj);
868 0           pdfmake_page_add_image(page, img_name, wm->data.image.image_obj);
869             }
870            
871             /* Generate watermark content */
872 3           pdfmake_buf_init(&buf);
873            
874 3           generate_watermark_content(wm, page, &buf,
875 3 50         gs_name[0] ? gs_name : NULL,
876             font_res_name, img_name);
877            
878             /* Merge watermark with existing page content (overlay/underlay). */
879 3           wm_data = pdfmake_buf_data(&buf);
880 3           wm_len = pdfmake_buf_len(&buf);
881              
882 3 100         if (page->has_content && page->contents_num) {
    50          
883 1           old_obj = pdfmake_doc_get(doc, page->contents_num);
884 1 50         if (old_obj && old_obj->kind == PDFMAKE_STREAM && old_obj->as.stream) {
    50          
    50          
885 1           old_data = old_obj->as.stream->raw;
886 1           old_len = old_obj->as.stream->raw_len;
887             }
888             }
889              
890 3 100         if (!old_data || old_len == 0) {
    50          
891 2           pdfmake_page_set_content(page, wm_data, wm_len);
892             } else {
893 1           pdfmake_buf_init(&merged);
894              
895 1 50         if (wm->opts.as_overlay) {
896 0           pdfmake_buf_append(&merged, old_data, old_len);
897 0           pdfmake_buf_append_byte(&merged, '\n');
898 0           pdfmake_buf_append(&merged, wm_data, wm_len);
899             } else {
900 1           pdfmake_buf_append(&merged, wm_data, wm_len);
901 1           pdfmake_buf_append_byte(&merged, '\n');
902 1           pdfmake_buf_append(&merged, old_data, old_len);
903             }
904              
905 1           pdfmake_page_set_content(page, pdfmake_buf_data(&merged), pdfmake_buf_len(&merged));
906 1           pdfmake_buf_free(&merged);
907             }
908            
909 3           pdfmake_buf_free(&buf);
910            
911 3           return PDFMAKE_OK;
912             }
913              
914 2           pdfmake_err_t pdfmake_doc_add_watermark(pdfmake_doc_t *doc,
915             pdfmake_watermark_t *wm)
916             {
917             size_t page_count;
918             size_t i;
919             pdfmake_page_t *page;
920             pdfmake_err_t err;
921              
922 2 50         if (!doc || !wm) return PDFMAKE_EINVAL;
    50          
923            
924 2           page_count = pdfmake_doc_page_count(doc);
925            
926 5 100         for (i = 0; i < page_count; i++) {
927 3           page = pdfmake_doc_get_page(doc, i);
928 3 50         if (page) {
929 3           err = pdfmake_page_add_watermark(page, wm);
930 3 50         if (err != PDFMAKE_OK) return err;
931             }
932             }
933            
934 2           return PDFMAKE_OK;
935             }
936              
937 0           pdfmake_err_t pdfmake_doc_add_watermark_range(pdfmake_doc_t *doc,
938             pdfmake_watermark_t *wm,
939             int start_page,
940             int end_page)
941             {
942             size_t page_count;
943             int i;
944             pdfmake_page_t *page;
945             pdfmake_err_t err;
946              
947 0 0         if (!doc || !wm) return PDFMAKE_EINVAL;
    0          
948            
949 0           page_count = pdfmake_doc_page_count(doc);
950            
951 0 0         if (start_page < 0) start_page = 0;
952 0 0         if (end_page < 0 || (size_t)end_page >= page_count) {
    0          
953 0           end_page = (int)page_count - 1;
954             }
955            
956 0 0         for (i = start_page; i <= end_page; i++) {
957 0           page = pdfmake_doc_get_page(doc, i);
958 0 0         if (page) {
959 0           err = pdfmake_page_add_watermark(page, wm);
960 0 0         if (err != PDFMAKE_OK) return err;
961             }
962             }
963            
964 0           return PDFMAKE_OK;
965             }
966              
967             /*----------------------------------------------------------------------------
968             * Stamp application
969             *--------------------------------------------------------------------------*/
970              
971 2           pdfmake_err_t pdfmake_doc_add_stamp(pdfmake_doc_t *doc,
972             pdfmake_stamp_t *stamp)
973             {
974             size_t page_count;
975             size_t i;
976             pdfmake_page_t *page;
977             char font_name[32];
978             const char *base_font;
979             pdfmake_buf_t buf;
980              
981 2 50         if (!doc || !stamp) return PDFMAKE_EINVAL;
    50          
982            
983 2           page_count = pdfmake_doc_page_count(doc);
984            
985             /* Reset Bates counter */
986 2 100         if (stamp->type == PDFMAKE_WM_STAMP_BATES) {
987 1           stamp->data.bates.current_number = stamp->data.bates.start_number;
988             }
989            
990 5 100         for (i = 0; i < page_count; i++) {
991 3           page = pdfmake_doc_get_page(doc, i);
992 3 50         if (!page) continue;
993            
994             /* Add font to page */
995 3           strcpy(font_name, "StampF1");
996 3 50         base_font = stamp->opts.font_name ? stamp->opts.font_name : "Helvetica";
997 3           pdfmake_page_add_font(page, font_name, base_font);
998            
999             /* Generate stamp content */
1000 3           pdfmake_buf_init(&buf);
1001            
1002 3           generate_stamp_content(stamp, page, (int)i + 1, (int)page_count,
1003             &buf, font_name);
1004            
1005             /* Append to page content */
1006             /* Note: Simplified - would merge with existing content */
1007 3           pdfmake_page_set_content(page, pdfmake_buf_data(&buf), pdfmake_buf_len(&buf));
1008            
1009 3           pdfmake_buf_free(&buf);
1010             }
1011            
1012 2           return PDFMAKE_OK;
1013             }
1014              
1015 0           pdfmake_err_t pdfmake_doc_add_stamp_range(pdfmake_doc_t *doc,
1016             pdfmake_stamp_t *stamp,
1017             int start_page,
1018             int end_page)
1019             {
1020             size_t page_count;
1021             int i;
1022             pdfmake_page_t *page;
1023             char font_name[32];
1024             const char *base_font;
1025             pdfmake_buf_t buf;
1026              
1027 0 0         if (!doc || !stamp) return PDFMAKE_EINVAL;
    0          
1028            
1029 0           page_count = pdfmake_doc_page_count(doc);
1030            
1031 0 0         if (start_page < 0) start_page = 0;
1032 0 0         if (end_page < 0 || (size_t)end_page >= page_count) {
    0          
1033 0           end_page = (int)page_count - 1;
1034             }
1035            
1036             /* Reset Bates counter */
1037 0 0         if (stamp->type == PDFMAKE_WM_STAMP_BATES) {
1038 0           stamp->data.bates.current_number = stamp->data.bates.start_number;
1039             }
1040            
1041 0 0         for (i = start_page; i <= end_page; i++) {
1042 0           page = pdfmake_doc_get_page(doc, i);
1043 0 0         if (!page) continue;
1044            
1045             /* Add font to page */
1046 0           strcpy(font_name, "StampF1");
1047 0 0         base_font = stamp->opts.font_name ? stamp->opts.font_name : "Helvetica";
1048 0           pdfmake_page_add_font(page, font_name, base_font);
1049            
1050             /* Generate stamp content */
1051 0           pdfmake_buf_init(&buf);
1052            
1053 0           generate_stamp_content(stamp, page, i + 1, (int)page_count,
1054             &buf, font_name);
1055            
1056             /* Append to page content */
1057 0           pdfmake_page_set_content(page, pdfmake_buf_data(&buf), pdfmake_buf_len(&buf));
1058            
1059 0           pdfmake_buf_free(&buf);
1060             }
1061            
1062 0           return PDFMAKE_OK;
1063             }