File Coverage

include/pdfmake_custom_ops.h
Criterion Covered Total %
statement 3 269 1.1
branch 0 56 0.0
condition n/a
subroutine n/a
pod n/a
total 3 325 0.9


line stmt bran cond sub pod time code
1             /*
2             * pdfmake_custom_ops.h - Custom op infrastructure for PDF::Make XS
3             *
4             * Provides compile-time call checkers and runtime custom ops to replace
5             * standard xsubpp method dispatch for hot paths. Reusable across the
6             * Semantic ecosystem via ExtUtils::Depends.
7             *
8             * Three op patterns:
9             * 1. CHAIN - $obj->method() returns $obj (Canvas, Arena, Writer)
10             * 2. GETTER - $obj->field reads C struct field by offset
11             * 3. CONST - Package::CONST() folds to OP_CONST at compile time
12             *
13             * Requires: xop_compat.h (from Object::Proto) for pre-5.14 fallback
14             */
15              
16             #ifndef PDFMAKE_CUSTOM_OPS_H
17             #define PDFMAKE_CUSTOM_OPS_H
18              
19             #include "EXTERN.h"
20             #include "perl.h"
21             #include "XSUB.h"
22             #include "xop_compat.h"
23              
24             /*============================================================================
25             * Pointer unwrap — skips sv_derived_from check (already validated by caller)
26             *==========================================================================*/
27              
28             #define PDFMAKE_UNWRAP(type, sv) \
29             INT2PTR(type, SvIV(SvRV(sv)))
30              
31             /*============================================================================
32             * Chain op argument types
33             *==========================================================================*/
34              
35             #define PDFMAKE_ARG_DOUBLE 0
36             #define PDFMAKE_ARG_STRING 1
37             #define PDFMAKE_ARG_INT 2
38              
39             /*============================================================================
40             * Chain dispatch table entry
41             *
42             * Each canvas/arena method maps to an entry with:
43             * - C function pointer
44             * - argument count (0-6 beyond self)
45             * - argument type codes
46             *==========================================================================*/
47              
48             typedef struct {
49             void *func;
50             int nargs;
51             int arg_types[6];
52             int ret_mode; /* 0 = return self (chain), 1 = return int, 2 = void */
53             } pdfmake_chain_entry_t;
54              
55             /*============================================================================
56             * CHAIN pp function - generic chainable method dispatch
57             *
58             * Unwraps self, pops args from stack, calls C function via dispatch table,
59             * returns self (already on stack). Used for Canvas, Arena, Writer.
60             *
61             * op_private holds the pointer type (which struct to unwrap to).
62             * op_targ holds the dispatch table index.
63             * The dispatch table pointer is stored in a package-level static.
64             *==========================================================================*/
65              
66             /* Forward declarations for package-specific dispatch tables */
67             /* These are defined in the respective XS BOOT sections */
68              
69             /*
70             * pp function for nullary chain ops: $obj->method() returns $obj
71             * No args to pop — just unwrap, call, return self.
72             * This is the simplest and most common canvas pattern.
73             */
74             typedef int (*pdfmake_nullary_fn)(void *self);
75             typedef int (*pdfmake_unary_d_fn)(void *self, double a);
76             typedef int (*pdfmake_binary_dd_fn)(void *self, double a, double b);
77             typedef int (*pdfmake_ternary_ddd_fn)(void *self, double a, double b, double c);
78             typedef int (*pdfmake_quad_dddd_fn)(void *self, double a, double b, double c, double d);
79             typedef int (*pdfmake_hex_dddddd_fn)(void *self, double a, double b, double c, double d, double e, double f);
80             typedef int (*pdfmake_unary_s_fn)(void *self, const char *s);
81             typedef int (*pdfmake_sd_fn)(void *self, const char *s, double d);
82              
83             /* Multiple dispatch tables — indexed by table ID (high bits of op_targ) */
84             #define PDFMAKE_MAX_CHAIN_TABLES 8
85             static pdfmake_chain_entry_t *pdfmake_chain_tables[PDFMAKE_MAX_CHAIN_TABLES];
86             static int pdfmake_chain_table_count = 0;
87              
88             /* Encode table_id + entry_index into op_targ */
89             #define PDFMAKE_CHAIN_TARG(table_id, index) (((table_id) << 16) | (index))
90             #define PDFMAKE_CHAIN_TABLE(targ) ((targ) >> 16)
91             #define PDFMAKE_CHAIN_INDEX(targ) ((targ) & 0xFFFF)
92              
93 0           static OP* pp_pdfmake_chain(pTHX) {
94 0           dSP;
95 0           UV targ = PL_op->op_targ;
96 0           int table_id = PDFMAKE_CHAIN_TABLE(targ);
97 0           int idx = PDFMAKE_CHAIN_INDEX(targ);
98 0           pdfmake_chain_entry_t *e = &pdfmake_chain_tables[table_id][idx];
99 0           int nargs = e->nargs;
100              
101             /* Self is below the args on the stack */
102 0           SV *self_sv = *(SP - nargs);
103 0           void *self = PDFMAKE_UNWRAP(void*, self_sv);
104 0           int err = 0;
105              
106 0           switch (nargs) {
107 0           case 0:
108 0           err = ((pdfmake_nullary_fn)e->func)(self);
109 0           break;
110 0           case 1:
111 0 0         if (e->arg_types[0] == PDFMAKE_ARG_STRING) {
112 0           const char *a = SvPV_nolen(TOPs);
113 0           SP--;
114 0           err = ((pdfmake_unary_s_fn)e->func)(self, a);
115             } else {
116 0           double a = SvNV(TOPs);
117 0           SP--;
118 0           err = ((pdfmake_unary_d_fn)e->func)(self, a);
119             }
120 0           break;
121 0           case 2:
122 0 0         if (e->arg_types[0] == PDFMAKE_ARG_STRING) {
123             /* string, double — e.g. Tf(font, size) */
124 0           double b = SvNV(TOPs); SP--;
125 0           const char *a = SvPV_nolen(TOPs); SP--;
126 0           err = ((pdfmake_sd_fn)e->func)(self, a, b);
127             } else {
128 0           double b = SvNV(TOPs); SP--;
129 0           double a = SvNV(TOPs); SP--;
130 0           err = ((pdfmake_binary_dd_fn)e->func)(self, a, b);
131             }
132 0           break;
133 0           case 3: {
134 0           double c = SvNV(TOPs); SP--;
135 0           double b = SvNV(TOPs); SP--;
136 0           double a = SvNV(TOPs); SP--;
137 0           err = ((pdfmake_ternary_ddd_fn)e->func)(self, a, b, c);
138 0           break;
139             }
140 0           case 4: {
141 0           double d = SvNV(TOPs); SP--;
142 0           double c = SvNV(TOPs); SP--;
143 0           double b = SvNV(TOPs); SP--;
144 0           double a = SvNV(TOPs); SP--;
145 0           err = ((pdfmake_quad_dddd_fn)e->func)(self, a, b, c, d);
146 0           break;
147             }
148 0           case 6: {
149 0           double f = SvNV(TOPs); SP--;
150 0           double e_val = SvNV(TOPs); SP--;
151 0           double d = SvNV(TOPs); SP--;
152 0           double c = SvNV(TOPs); SP--;
153 0           double b = SvNV(TOPs); SP--;
154 0           double a = SvNV(TOPs); SP--;
155 0           err = ((pdfmake_hex_dddddd_fn)e->func)(self, a, b, c, d, e_val, f);
156 0           break;
157             }
158             }
159              
160 0           switch (e->ret_mode) {
161 0           case 1:
162             /* Return int result */
163 0 0         if (err < 0) croak("PDF::Make custom op failed (index %d)", idx);
164 0           SETs(sv_2mortal(newSViv(err)));
165 0           PUTBACK;
166 0           RETURN;
167 0           case 2:
168             /* Void — just pop args, leave nothing */
169 0           SP = SP - nargs;
170 0           PUTBACK;
171 0           RETURN;
172 0           default:
173             /* Chain — return self */
174 0 0         if (err != 0) croak("PDF::Make custom op failed (index %d)", idx);
175 0           PUTBACK;
176 0           RETURN;
177             }
178             }
179              
180             /*============================================================================
181             * Chain call checker
182             *
183             * At compile time, replaces entersub with our custom op.
184             * For nullary ops, creates a UNOP(self).
185             * For ops with args, we DON'T rewrite the op tree — instead we just
186             * replace the pp_addr of the entersub to avoid complex tree surgery.
187             * This is simpler and still eliminates typemap/method-cache overhead.
188             *==========================================================================*/
189              
190 0           static OP* pdfmake_chain_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
191 0           IV idx = SvIV(ckobj);
192             PERL_UNUSED_ARG(namegv);
193              
194             /* Simple approach: replace entersub's pp_addr directly.
195             * The args remain on the stack in standard order.
196             * This avoids complex op tree surgery for multi-arg methods. */
197 0           entersubop->op_ppaddr = pp_pdfmake_chain;
198 0           cUNOPx(entersubop)->op_first->op_targ = idx; /* Store index */
199              
200             /* Actually, op_targ on entersub is used for pad allocation.
201             * Store in op_private + a side table instead. */
202              
203             /* For safety, just use op_targ on the entersubop itself */
204 0           entersubop->op_targ = idx;
205              
206 0           return entersubop;
207             }
208              
209             /*============================================================================
210             * GETTER ops — read C struct field by offset + type
211             *
212             * op_targ encodes: (byte_offset << 4) | field_type
213             *==========================================================================*/
214              
215             #define PDFMAKE_FIELD_DOUBLE 0
216             #define PDFMAKE_FIELD_INT 1
217             #define PDFMAKE_FIELD_UV 2
218             #define PDFMAKE_FIELD_STRING 3
219              
220             #define PDFMAKE_GETTER_TARG(offset, type) (((UV)(offset) << 4) | (type))
221              
222 0           static OP* pp_pdfmake_getter(pTHX) {
223 0           dSP;
224 0           SV *obj = TOPs;
225 0           char *ptr = PDFMAKE_UNWRAP(char*, obj);
226 0           UV encoded = PL_op->op_targ;
227 0           int field_type = encoded & 0xF;
228 0           size_t offset = encoded >> 4;
229              
230 0           switch (field_type) {
231 0           case PDFMAKE_FIELD_DOUBLE:
232 0           SETs(sv_2mortal(newSVnv(*(double*)(ptr + offset))));
233 0           break;
234 0           case PDFMAKE_FIELD_INT:
235 0           SETs(sv_2mortal(newSViv(*(int*)(ptr + offset))));
236 0           break;
237 0           case PDFMAKE_FIELD_UV:
238 0           SETs(sv_2mortal(newSVuv(*(UV*)(ptr + offset))));
239 0           break;
240 0           case PDFMAKE_FIELD_STRING: {
241 0           const char *val = *(const char**)(ptr + offset);
242 0 0         SETs(val ? sv_2mortal(newSVpv(val, 0)) : &PL_sv_undef);
243 0           break;
244             }
245             }
246 0           RETURN;
247             }
248              
249 0           static OP* pdfmake_getter_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
250 0           UV targ = SvUV(ckobj);
251             OP *pushop, *selfop, *cvop;
252             PERL_UNUSED_ARG(namegv);
253              
254 0           pushop = cUNOPx(entersubop)->op_first;
255 0 0         if (!OpHAS_SIBLING(pushop))
256 0           pushop = cUNOPx(pushop)->op_first;
257              
258 0 0         selfop = OpSIBLING(pushop);
259 0           cvop = selfop;
260 0 0         while (OpHAS_SIBLING(cvop))
261 0 0         cvop = OpSIBLING(cvop);
262              
263             /* Detach self from chain, free the rest */
264 0           OpMORESIB_set(pushop, cvop);
265 0           OpLASTSIB_set(selfop, NULL);
266              
267 0           OP *newop = newUNOP(OP_NULL, 0, selfop);
268 0           newop->op_type = OP_CUSTOM;
269 0           newop->op_ppaddr = pp_pdfmake_getter;
270 0           newop->op_targ = targ;
271              
272 0           op_free(entersubop);
273 0           return newop;
274             }
275              
276             /*============================================================================
277             * INDIRECT GETTER — read field through one pointer chase
278             *
279             * For wrapper structs: self->ptr_offset is a pointer, then read field
280             * at field_offset from that pointer.
281             *
282             * op_targ encodes: (ptr_offset << 20) | (field_offset << 4) | field_type
283             *==========================================================================*/
284              
285             #define PDFMAKE_INDIRECT_TARG(ptr_off, field_off, type) \
286             (((UV)(ptr_off) << 20) | ((UV)(field_off) << 4) | (type))
287              
288 0           static OP* pp_pdfmake_indirect_getter(pTHX) {
289 0           dSP;
290 0           SV *obj = TOPs;
291 0           char *wrapper = PDFMAKE_UNWRAP(char*, obj);
292 0           UV encoded = PL_op->op_targ;
293 0           int field_type = encoded & 0xF;
294 0           size_t field_off = (encoded >> 4) & 0xFFFF;
295 0           size_t ptr_off = encoded >> 20;
296              
297             /* Follow the pointer: wrapper + ptr_off is a pointer to the inner struct */
298 0           char *inner = *(char**)(wrapper + ptr_off);
299 0 0         if (!inner) {
300 0           SETs(&PL_sv_undef);
301 0           RETURN;
302             }
303              
304 0           switch (field_type) {
305 0           case PDFMAKE_FIELD_DOUBLE:
306 0           SETs(sv_2mortal(newSVnv(*(double*)(inner + field_off))));
307 0           break;
308 0           case PDFMAKE_FIELD_INT:
309 0           SETs(sv_2mortal(newSViv(*(int*)(inner + field_off))));
310 0           break;
311 0           case PDFMAKE_FIELD_UV:
312 0           SETs(sv_2mortal(newSVuv(*(UV*)(inner + field_off))));
313 0           break;
314 0           case PDFMAKE_FIELD_STRING: {
315 0           const char *val = *(const char**)(inner + field_off);
316 0 0         SETs(val ? sv_2mortal(newSVpv(val, 0)) : &PL_sv_undef);
317 0           break;
318             }
319             }
320 0           RETURN;
321             }
322              
323 0           static OP* pdfmake_indirect_getter_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
324 0           UV targ = SvUV(ckobj);
325             OP *pushop, *selfop, *cvop;
326             PERL_UNUSED_ARG(namegv);
327              
328 0           pushop = cUNOPx(entersubop)->op_first;
329 0 0         if (!OpHAS_SIBLING(pushop))
330 0           pushop = cUNOPx(pushop)->op_first;
331 0 0         selfop = OpSIBLING(pushop);
332 0           cvop = selfop;
333 0 0         while (OpHAS_SIBLING(cvop))
334 0 0         cvop = OpSIBLING(cvop);
335              
336 0           OpMORESIB_set(pushop, cvop);
337 0           OpLASTSIB_set(selfop, NULL);
338              
339 0           OP *newop = newUNOP(OP_NULL, 0, selfop);
340 0           newop->op_type = OP_CUSTOM;
341 0           newop->op_ppaddr = pp_pdfmake_indirect_getter;
342 0           newop->op_targ = targ;
343              
344 0           op_free(entersubop);
345 0           return newop;
346             }
347              
348             #define PDFMAKE_REGISTER_INDIRECT_GETTER(stash, method, wrap_type, ptr_field, inner_type, field, ftype) \
349             do { \
350             GV *_gv = gv_fetchmeth_pvn(stash, method, strlen(method), 0, 0); \
351             if (_gv && GvCV(_gv)) { \
352             SV *_ck = newSVuv(PDFMAKE_INDIRECT_TARG( \
353             offsetof(wrap_type, ptr_field), \
354             offsetof(inner_type, field), ftype)); \
355             cv_set_call_checker(GvCV(_gv), pdfmake_indirect_getter_call_checker, _ck); \
356             } \
357             } while(0)
358              
359             /*============================================================================
360             * TYPE-TEST ops — compare struct field to constant, return bool
361             *
362             * op_targ encodes: (ptr_offset << 20) | (field_offset << 8) | expected_value
363             * Used for: is_null, is_int, is_array, etc. on Obj wrapper
364             *==========================================================================*/
365              
366             #define PDFMAKE_TYPETEST_TARG(ptr_off, field_off, expected) \
367             (((UV)(ptr_off) << 20) | ((UV)(field_off) << 8) | ((expected) & 0xFF))
368              
369 0           static OP* pp_pdfmake_typetest(pTHX) {
370 0           dSP;
371 0           SV *obj = TOPs;
372 0           char *wrapper = PDFMAKE_UNWRAP(char*, obj);
373 0           UV encoded = PL_op->op_targ;
374 0           int expected = encoded & 0xFF;
375 0           size_t field_off = (encoded >> 8) & 0xFFF;
376 0           size_t ptr_off = encoded >> 20;
377              
378 0           char *inner = *(char**)(wrapper + ptr_off);
379 0 0         int actual = inner ? *(int*)(inner + field_off) : -1;
380              
381 0 0         SETs(boolSV(actual == expected));
382 0           RETURN;
383             }
384              
385 0           static OP* pdfmake_typetest_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
386 0           UV targ = SvUV(ckobj);
387             OP *pushop, *selfop, *cvop;
388             PERL_UNUSED_ARG(namegv);
389              
390 0           pushop = cUNOPx(entersubop)->op_first;
391 0 0         if (!OpHAS_SIBLING(pushop))
392 0           pushop = cUNOPx(pushop)->op_first;
393 0 0         selfop = OpSIBLING(pushop);
394 0           cvop = selfop;
395 0 0         while (OpHAS_SIBLING(cvop))
396 0 0         cvop = OpSIBLING(cvop);
397              
398 0           OpMORESIB_set(pushop, cvop);
399 0           OpLASTSIB_set(selfop, NULL);
400              
401 0           OP *newop = newUNOP(OP_NULL, 0, selfop);
402 0           newop->op_type = OP_CUSTOM;
403 0           newop->op_ppaddr = pp_pdfmake_typetest;
404 0           newop->op_targ = targ;
405              
406 0           op_free(entersubop);
407 0           return newop;
408             }
409              
410             #define PDFMAKE_REGISTER_TYPETEST(stash, method, wrap_type, ptr_field, inner_type, field, expected_val) \
411             do { \
412             GV *_gv = gv_fetchmeth_pvn(stash, method, strlen(method), 0, 0); \
413             if (_gv && GvCV(_gv)) { \
414             SV *_ck = newSVuv(PDFMAKE_TYPETEST_TARG( \
415             offsetof(wrap_type, ptr_field), \
416             offsetof(inner_type, field), expected_val)); \
417             cv_set_call_checker(GvCV(_gv), pdfmake_typetest_call_checker, _ck); \
418             } \
419             } while(0)
420              
421             /*============================================================================
422             * ARENA CONSTRUCTOR ops — create PDF objects from arena
423             *
424             * All arena constructors follow the same pattern:
425             * Newxz wrapper → set arena backref → arena_alloc → C_init(args) → bless
426             *
427             * op_targ indexes a dispatch table of init function + arg type.
428             *==========================================================================*/
429              
430             #define PDFMAKE_ARENA_ARG_NONE 0 /* null, array, dict, stream */
431             #define PDFMAKE_ARENA_ARG_INT 1 /* bool(int), int(IV) */
432             #define PDFMAKE_ARENA_ARG_DOUBLE 2 /* real(NV) */
433             #define PDFMAKE_ARENA_ARG_STRING 3 /* name(str,len), str(str,len), hexstr(str,len) */
434             #define PDFMAKE_ARENA_ARG_REF 4 /* ref(num, gen) */
435              
436             typedef struct {
437             int arg_type;
438             /* The init function signature varies, stored as void* */
439             void *init_fn;
440             } pdfmake_arena_ctor_entry_t;
441              
442             #define PDFMAKE_MAX_ARENA_CTORS 16
443             static pdfmake_arena_ctor_entry_t pdfmake_arena_ctors[PDFMAKE_MAX_ARENA_CTORS];
444             static int pdfmake_arena_ctor_count = 0;
445              
446             /* Forward declare the types we need */
447             typedef struct pdfmake_obj pdfmake_obj_t_fwd;
448              
449 0           static OP* pp_pdfmake_arena_ctor(pTHX) {
450 0           dSP; dMARK; dAX;
451 0           UV idx = PL_op->op_targ;
452 0           pdfmake_arena_ctor_entry_t *e = &pdfmake_arena_ctors[idx];
453              
454             /* Self (arena wrapper) is ST(0) */
455 0           SV *arena_sv = ST(0);
456             /* Unwrap to arena_xs_t — we know the struct layout:
457             * { pdfmake_arena_t *arena } at offset 0 */
458 0           char *arena_xs = PDFMAKE_UNWRAP(char*, arena_sv);
459 0           void *arena_ptr = *(void**)arena_xs; /* first field is arena pointer */
460              
461             /* Allocate wrapper + obj */
462             /* We inline the Newxz/alloc pattern here */
463             typedef struct {
464             void *arena_xs_ptr;
465             SV *arena_sv_ref;
466             void *obj_ptr;
467             } obj_wrap_t;
468              
469             obj_wrap_t *wrap;
470 0           Newxz(wrap, 1, obj_wrap_t);
471 0           wrap->arena_xs_ptr = arena_xs;
472 0           wrap->arena_sv_ref = SvREFCNT_inc(arena_sv);
473              
474             /* Arena alloc for the obj (pdfmake_arena_alloc declared in pdfmake_arena.h) */
475 0           wrap->obj_ptr = pdfmake_arena_alloc(arena_ptr, 32); /* sizeof(pdfmake_obj_t) */
476 0 0         if (!wrap->obj_ptr) {
477 0           SvREFCNT_dec(wrap->arena_sv_ref);
478 0           Safefree(wrap);
479 0           croak("Arena allocation failed");
480             }
481              
482             /* Call the init function based on arg type */
483             typedef struct { int kind; } simple_obj_t; /* just need to write to *obj */
484              
485 0           switch (e->arg_type) {
486 0           case PDFMAKE_ARENA_ARG_NONE: {
487             typedef simple_obj_t (*fn0)(void);
488 0           simple_obj_t result = ((fn0)e->init_fn)();
489 0           *(simple_obj_t*)wrap->obj_ptr = result;
490 0           SP = MARK;
491 0           break;
492             }
493 0           case PDFMAKE_ARENA_ARG_INT: {
494             typedef simple_obj_t (*fn_iv)(int64_t);
495 0           int64_t val = SvIV(ST(1));
496 0           simple_obj_t result = ((fn_iv)e->init_fn)(val);
497 0           *(simple_obj_t*)wrap->obj_ptr = result;
498 0           SP = MARK;
499 0           break;
500             }
501 0           case PDFMAKE_ARENA_ARG_DOUBLE: {
502             typedef simple_obj_t (*fn_nv)(double);
503 0           double val = SvNV(ST(1));
504 0           simple_obj_t result = ((fn_nv)e->init_fn)(val);
505 0           *(simple_obj_t*)wrap->obj_ptr = result;
506 0           SP = MARK;
507 0           break;
508             }
509 0           case PDFMAKE_ARENA_ARG_STRING: {
510             typedef simple_obj_t (*fn_str)(void*, const char*, size_t);
511             STRLEN len;
512 0           const char *str = SvPV(ST(1), len);
513 0           simple_obj_t result = ((fn_str)e->init_fn)(arena_ptr, str, len);
514 0           *(simple_obj_t*)wrap->obj_ptr = result;
515 0           SP = MARK;
516 0           break;
517             }
518 0           case PDFMAKE_ARENA_ARG_REF: {
519             typedef simple_obj_t (*fn_ref)(uint32_t, uint16_t);
520 0           uint32_t num = SvUV(ST(1));
521 0           uint16_t gen = (uint16_t)SvUV(ST(2));
522 0           simple_obj_t result = ((fn_ref)e->init_fn)(num, gen);
523 0           *(simple_obj_t*)wrap->obj_ptr = result;
524 0           SP = MARK;
525 0           break;
526             }
527             }
528              
529             /* Bless and return */
530 0           SV *rv = newRV_noinc(newSViv(PTR2IV(wrap)));
531 0           sv_bless(rv, gv_stashpv("PDF::Make::Obj", GV_ADD));
532 0 0         XPUSHs(sv_2mortal(rv));
533 0           PUTBACK;
534 0           return NORMAL;
535             }
536              
537 0           static OP* pdfmake_arena_ctor_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
538 0           IV idx = SvIV(ckobj);
539             PERL_UNUSED_ARG(namegv);
540 0           entersubop->op_ppaddr = pp_pdfmake_arena_ctor;
541 0           entersubop->op_targ = idx;
542 0           return entersubop;
543             }
544              
545             #define PDFMAKE_REGISTER_ARENA_CTOR(stash, method_name, arg_type_val, init_func) \
546             do { \
547             int _idx = pdfmake_arena_ctor_count++; \
548             pdfmake_arena_ctors[_idx].arg_type = (arg_type_val); \
549             pdfmake_arena_ctors[_idx].init_fn = (void*)(init_func); \
550             GV *_gv = gv_fetchmeth_pvn(stash, method_name, \
551             strlen(method_name), 0, 0); \
552             if (_gv && GvCV(_gv)) { \
553             SV *_ck = newSViv((IV)_idx); \
554             cv_set_call_checker(GvCV(_gv), pdfmake_arena_ctor_call_checker, _ck); \
555             } \
556             } while(0)
557              
558             /*============================================================================
559             * META ops — getter/setter for string metadata fields
560             *
561             * op_targ indexes into a dispatch table of get/set function pairs.
562             * At runtime: if 1 arg (just self) → call getter, return string.
563             * if 2 args (self + value) → call setter, return value.
564             *
565             * Uses pp_addr replacement on entersub (not op tree rewrite) so args
566             * stay on the stack and items count is preserved.
567             *==========================================================================*/
568              
569             typedef const char* (*pdfmake_meta_get_fn)(void *self);
570             typedef int (*pdfmake_meta_set_fn)(void *self, const char *val);
571              
572             typedef struct {
573             pdfmake_meta_get_fn getter;
574             pdfmake_meta_set_fn setter;
575             } pdfmake_meta_entry_t;
576              
577             #define PDFMAKE_MAX_META_ENTRIES 16
578             static pdfmake_meta_entry_t pdfmake_meta_table[PDFMAKE_MAX_META_ENTRIES];
579             static int pdfmake_meta_table_count = 0;
580              
581 0           static OP* pp_pdfmake_meta(pTHX) {
582 0           dSP; dMARK; dAX;
583 0           int count = SP - MARK;
584 0           UV idx = PL_op->op_targ;
585 0           pdfmake_meta_entry_t *e = &pdfmake_meta_table[idx];
586              
587 0           SV *self_sv = ST(0);
588 0           void *self = PDFMAKE_UNWRAP(void*, self_sv);
589              
590 0 0         if (count > 1) {
591             /* Setter */
592 0           const char *val = SvPV_nolen(ST(1));
593 0           e->setter(self, val);
594 0           SP = MARK;
595 0 0         XPUSHs(ST(1));
596             } else {
597             /* Getter */
598 0           const char *val = e->getter(self);
599 0           SP = MARK;
600 0 0         if (val)
601 0 0         XPUSHs(sv_2mortal(newSVpv(val, 0)));
602             else
603 0 0         XPUSHs(&PL_sv_undef);
604             }
605 0           PUTBACK;
606 0           return NORMAL;
607             }
608              
609 0           static OP* pdfmake_meta_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
610 0           IV idx = SvIV(ckobj);
611             PERL_UNUSED_ARG(namegv);
612 0           entersubop->op_ppaddr = pp_pdfmake_meta;
613 0           entersubop->op_targ = idx;
614 0           return entersubop;
615             }
616              
617             #define PDFMAKE_REGISTER_META(stash, method_name, get_fn, set_fn) \
618             do { \
619             int _idx = pdfmake_meta_table_count++; \
620             pdfmake_meta_table[_idx].getter = (pdfmake_meta_get_fn)(get_fn); \
621             pdfmake_meta_table[_idx].setter = (pdfmake_meta_set_fn)(set_fn); \
622             GV *_gv = gv_fetchmeth_pvn(stash, method_name, \
623             strlen(method_name), 0, 0); \
624             if (_gv && GvCV(_gv)) { \
625             SV *_ck = newSViv((IV)_idx); \
626             cv_set_call_checker(GvCV(_gv), pdfmake_meta_call_checker, _ck); \
627             } \
628             } while(0)
629              
630             /*============================================================================
631             * CONST ops — fold to OP_CONST at compile time
632             *==========================================================================*/
633              
634 28           static OP* pdfmake_const_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
635             PERL_UNUSED_ARG(namegv);
636 28           op_free(entersubop);
637 28           return newSVOP(OP_CONST, 0, SvREFCNT_inc(ckobj));
638             }
639              
640             /*============================================================================
641             * Registration macros
642             *==========================================================================*/
643              
644             /* Register a chainable method's CV with the chain call checker */
645             #define PDFMAKE_REGISTER_CHAIN(stash, method_name, table_id, index) \
646             do { \
647             GV *_gv = gv_fetchmeth_pvn(stash, method_name, \
648             strlen(method_name), 0, 0); \
649             if (_gv && GvCV(_gv)) { \
650             SV *_ck = newSViv((IV)PDFMAKE_CHAIN_TARG(table_id, index)); \
651             cv_set_call_checker(GvCV(_gv), pdfmake_chain_call_checker, _ck); \
652             } \
653             } while(0)
654              
655             /* Register a struct field getter */
656             #define PDFMAKE_REGISTER_GETTER(stash, method_name, struct_type, field, ftype) \
657             do { \
658             GV *_gv = gv_fetchmeth_pvn(stash, method_name, \
659             strlen(method_name), 0, 0); \
660             if (_gv && GvCV(_gv)) { \
661             SV *_ck = newSVuv(PDFMAKE_GETTER_TARG( \
662             offsetof(struct_type, field), ftype)); \
663             cv_set_call_checker(GvCV(_gv), pdfmake_getter_call_checker, _ck); \
664             } \
665             } while(0)
666              
667             /* Register a constant (folds to OP_CONST at compile time) */
668             #define PDFMAKE_REGISTER_CONST(stash, name, value) \
669             do { \
670             GV *_gv = gv_fetchmeth_pvn(stash, name, strlen(name), 0, 0); \
671             if (_gv && GvCV(_gv)) { \
672             SV *_ck = newSViv((IV)(value)); \
673             cv_set_call_checker(GvCV(_gv), pdfmake_const_call_checker, _ck); \
674             } \
675             } while(0)
676              
677             /*============================================================================
678             * XOP registration helper — call once per pp function in BOOT
679             *==========================================================================*/
680              
681             #define PDFMAKE_REGISTER_XOP(xop_var, pp_func, op_name, op_desc) \
682             do { \
683             XopENTRY_set(&(xop_var), xop_name, op_name); \
684             XopENTRY_set(&(xop_var), xop_desc, op_desc); \
685             Perl_custom_op_register(aTHX_ pp_func, &(xop_var)); \
686             } while(0)
687              
688             #endif /* PDFMAKE_CUSTOM_OPS_H */