File Coverage

include/in.h
Criterion Covered Total %
statement 58 59 98.3
branch 37 56 66.0
condition n/a
subroutine n/a
pod n/a
total 95 115 82.6


line stmt bran cond sub pod time code
1             /*
2             * in.h - Perl-XS bridge helpers for Colouring::In::XS
3             *
4             * Provides the glue between Perl SVs/HVs/AVs and the pure-C
5             * colouring_rgba_t from colouring.h. Header-only; requires
6             * PERL_NO_GET_CONTEXT, perl.h and XSUB.h already included.
7             *
8             * Re-usable by Eshu (CSS preprocessor) or any other XS module
9             * that works with Colouring::In colour objects.
10             */
11              
12             #ifndef COLOURING_IN_H
13             #define COLOURING_IN_H
14              
15             #include "colouring.h"
16              
17             /* ── Class name constant ──────────────────────────────────────── */
18              
19             #define COLOURING_CLASS "Colouring::In::XS"
20             #define COLOURING_CLASS_LEN 17
21              
22             /* ── Message store (set from Perl side) ───────────────────────── */
23             /* Holds a refcount-managed reference to the user's message hash so
24             * the underlying HV stays alive after set_messages() returns. */
25              
26             static SV * MESSAGES_REF = NULL;
27              
28             #define MESSAGES \
29             ((MESSAGES_REF && SvROK(MESSAGES_REF)) ? (HV*)SvRV(MESSAGES_REF) : NULL)
30              
31             /* ── Bless a hash into the caller's class ─────────────────────── */
32              
33 10152           static SV * xs_new(SV * class, HV * hash) {
34             dTHX;
35 10152 100         if (SvROK(class)) {
36 2 50         char * name = HvNAME(SvSTASH(SvRV(class)));
    50          
    50          
    0          
    50          
    50          
37 2           class = newSVpv(name, strlen(name));
38             }
39 10152           return sv_bless(newRV_noinc((SV*)hash), gv_stashsv(class, 0));
40             }
41              
42             /* ── Quick "does this SV look numeric?" check ─────────────────── */
43              
44 10161           static int numIs(SV * num) {
45             dTHX;
46 10161 50         if (!num || !SvOK(num)) return 0;
    50          
47             /* looks_like_number is Perl's portable, bounds-safe numeric test
48             * (handles ints, floats, scientific notation, Unicode digits). The
49             * previous hand-rolled scanner had an unbounded write into a
50             * fixed-size stack buffer — a stack-smash on long all-digit input. */
51 10161           return looks_like_number(num) ? 1 : 0;
52             }
53              
54             /* ── Scale a value that may be a percentage ───────────────────── */
55              
56 3           static double xs_scaled(SV * num, int size) {
57             dTHX;
58             STRLEN len;
59 3           char * number = SvPV(num, len);
60 3           double n = atof(number);
61             /* Percent suffix: check the last *character*, not the trailing
62             * NUL (`number[strlen(number)]` always reads '\0'). */
63 3 50         if (len > 0 && number[len - 1] == '%') {
    50          
64 0           return (n * size) / 100;
65             }
66 3           return n;
67             }
68              
69             /* ── Extract RGBA from a blessed Colouring::In::XS object ─────── */
70              
71 182           static colouring_rgba_t xs_extract_rgba(SV * self) {
72             dTHX;
73             colouring_rgba_t c;
74 182           AV * colour = (AV*)SvRV(*hv_fetch((HV*)SvRV(self), "colour", 6, 0));
75 182           int len = av_len(colour);
76             SV *r, *g, *b;
77              
78 182 50         r = len >= 0 ? *av_fetch(colour, 0, 0) : NULL;
79 182 50         g = len >= 1 ? *av_fetch(colour, 1, 0) : NULL;
80 182 50         b = len >= 2 ? *av_fetch(colour, 2, 0) : NULL;
81              
82 182 50         c.r = (r && SvOK(r)) ? SvNV(r) : 255;
    100          
83 182 50         c.g = (g && SvOK(g)) ? SvNV(g) : 255;
    100          
84 182 50         c.b = (b && SvOK(b)) ? SvNV(b) : 255;
    100          
85 182           c.a = SvNV(*hv_fetch((HV*)SvRV(self), "alpha", 5, 0));
86 182           return c;
87             }
88              
89             /* ── Convert a colour string into an AV ref of [r,g,b(,a)] ────── */
90              
91 10071           static SV * xs_convert_colour(const char * colour) {
92             dTHX;
93             colouring_rgba_t c;
94             AV * av;
95              
96 10071 100         if (!colouring_parse(colour, &c)) {
97 2           croak("Cannot convert the colour format");
98             return &PL_sv_undef;
99             }
100              
101 10068           av = newAV();
102 10068           av_push(av, newSVnv(c.r));
103 10068           av_push(av, newSVnv(c.g));
104 10068           av_push(av, newSVnv(c.b));
105 10068 100         if (c.a != 1.0) {
106 10           av_push(av, newSVnv(c.a));
107             }
108 10068           return newRV_noinc((SV*)av);
109             }
110              
111             /* ── Pack RGBA back into a blessed object ─────────────────────── */
112              
113 10155           static SV * xs_new_color(SV * class, SV * colour, SV * a) {
114             dTHX;
115 10155           HV * hash = newHV();
116             /* Branch on whether colour is a [r,g,b(,a)] arrayref or a string
117             * like "#fff" / "rgb(...)". Crucially, SvRV() is undefined on a
118             * non-reference SV — guard with SvROK first. */
119 10155 100         if (SvROK(colour) && SvTYPE(SvRV(colour)) == SVt_PVAV) {
    50          
120 84 100         if (av_len((AV*)SvRV(colour)) == 3) {
121 2           a = av_pop((AV*)SvRV(colour));
122             }
123 84           hv_store(hash, "colour", 6, newSVsv(colour), 0);
124             } else {
125 10071           colour = xs_convert_colour(SvPV_nolen(colour));
126 10068 100         if (av_len((AV*)SvRV(colour)) == 3) {
127 10           a = av_pop((AV*)SvRV(colour));
128             }
129 10068           hv_store(hash, "colour", 6, colour, 0);
130             }
131 10152 50         hv_store(hash, "alpha", 5, numIs(a) ? newSVsv(a) : newSViv(1), 0);
132 10152           return xs_new(class, hash);
133             }
134              
135             /* ── Build a new object from colouring_rgba_t ─────────────────── */
136              
137 70           static SV * xs_rgba_to_obj(SV * class, colouring_rgba_t c) {
138             dTHX;
139 70           AV * av = newAV();
140 70           av_push(av, newSVnv(c.r));
141 70           av_push(av, newSVnv(c.g));
142 70           av_push(av, newSVnv(c.b));
143 70           return xs_new_color(class, newRV_noinc((SV*)av), newSVnv(c.a));
144             }
145              
146             /* ── Ensure SV is a colour object (convert string -> obj) ─────── */
147              
148 70           static SV * xs_ensure_obj(SV * class, SV * colour) {
149             dTHX;
150 70 100         if (!SvROK(colour)) {
151 57           return xs_new_color(class, colour, newSVnv(1));
152             }
153 13           return colour;
154             }
155              
156             /* ── Convenience: class SV from constant ──────────────────────── */
157             /* Mortalised so callers don't have to remember to free it. The hot
158             * path (custom ops + helper methods) was previously leaking one SV
159             * per invocation. */
160              
161 13           static SV * xs_class_sv(void) {
162             dTHX;
163 13           return sv_2mortal(newSVpv(COLOURING_CLASS, COLOURING_CLASS_LEN));
164             }
165              
166             #endif /* COLOURING_IN_H */