File Coverage

lib/Print/Format.xs
Criterion Covered Total %
statement 811 1094 74.1
branch 572 1136 50.3
condition n/a
subroutine n/a
pod n/a
total 1383 2230 62.0


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4              
5 1           void export_proto (CV * cb, char * pkg, int pkg_len, char * method, int method_len, char * proto) {
6             dTHX;
7 1           int name_len = pkg_len + method_len + 3;
8 1           char *name = (char *)malloc(name_len);
9 1           snprintf(name, name_len, "%s::%s", pkg, method);
10 1           newXSproto(name, cb, __FILE__, proto);
11 1           free(name);
12 1           }
13              
14 0           bool is_cjk_codepoint(unsigned int codepoint) {
15 0 0         return (codepoint >= 0x1100 && codepoint <= 0x115F) ||
    0          
16 0 0         (codepoint >= 0x2E80 && codepoint <= 0x2EFF) ||
    0          
17 0 0         (codepoint >= 0x2F00 && codepoint <= 0x2FDF) ||
    0          
18 0 0         (codepoint >= 0x3000 && codepoint <= 0x303F) ||
    0          
19 0 0         (codepoint >= 0x3040 && codepoint <= 0x309F) ||
    0          
20 0 0         (codepoint >= 0x30A0 && codepoint <= 0x30FF) ||
    0          
21 0 0         (codepoint >= 0x3100 && codepoint <= 0x312F) ||
    0          
22 0 0         (codepoint >= 0x3130 && codepoint <= 0x318F) ||
    0          
23 0 0         (codepoint >= 0x3200 && codepoint <= 0x32FF) ||
    0          
24 0 0         (codepoint >= 0x3400 && codepoint <= 0x4DBF) ||
    0          
25 0 0         (codepoint >= 0x4E00 && codepoint <= 0x9FFF) ||
    0          
    0          
26 0 0         (codepoint >= 0xF900 && codepoint <= 0xFAFF);
27             }
28              
29 12           bool is_decorative_line(const char *line) {
30 12           size_t len = strlen(line);
31 12 50         if (len == 0) return false;
32 12           char first = line[0];
33 12 100         if (first != '*' && first != '|' && first != '-') return false;
    100          
    50          
34 5           return strspn(line, &first) == len;
35             }
36              
37 24           char* trim_line(char *line) {
38 24           char *end = line + strlen(line) - 1;
39 25 100         while (end > line && (*end == ' ' || *end == '\t' || *end == '\r')) {
    100          
    50          
    50          
40 1           *end = '\0';
41 1           end--;
42             }
43 24           return line;
44             }
45              
46 1           char** split_lines(const char *format_str, STRLEN len, int *line_count) {
47 1           int capacity = 10;
48 1           char **lines = (char**)malloc(capacity * sizeof(char*));
49 1           *line_count = 0;
50            
51 1           char *line_start = (char*)format_str;
52 1           char *current = (char*)format_str;
53            
54 664 100         while (current <= format_str + len) {
55 663 100         if (*current == '\n' || current == format_str + len) {
    100          
56 26           STRLEN line_len = current - line_start;
57 26 100         if (line_len > 0) {
58 24           char *line_copy = (char*)malloc(line_len + 1);
59 24           strncpy(line_copy, line_start, line_len);
60 24           line_copy[line_len] = '\0';
61 24           trim_line(line_copy);
62            
63 24 50         if (strlen(line_copy) > 0) {
64 24 100         if (*line_count >= capacity) {
65 2           capacity *= 2;
66 2           lines = (char**)realloc(lines, capacity * sizeof(char*));
67             }
68 24           lines[(*line_count)++] = line_copy;
69             } else {
70 0           free(line_copy);
71             }
72             }
73 26           line_start = current + 1;
74             }
75 663           current++;
76             }
77 1           return lines;
78             }
79              
80 1           HV* parse_raw_format (SV *format_sv) {
81             dTHX;
82             STRLEN len;
83 1           char *format_str = SvPV(format_sv, len);
84            
85 1           HV *parsed = newHV();
86 1           AV *line_pairs = newAV();
87            
88 1           int line_count = 0;
89 1           char **lines = split_lines(format_str, len, &line_count);
90            
91 1           int section_num = 0;
92 1           int i = 0;
93 13 100         while (i < line_count) {
94 12 50         if (strcmp(lines[i], "=") == 0) {
95 0           section_num++;
96 0           i++;
97 0           continue;
98             }
99 12 50         if (is_decorative_line(lines[i])) {
100 0           i++;
101 0           continue;
102             }
103            
104 12 50         if (i + 1 < line_count) {
105 12           char *spec_line = lines[i];
106 12           char *content_line = lines[i + 1];
107            
108 12           HV *pair = newHV();
109 12           hv_store(pair, "section", 7, newSViv(section_num), 0);
110 12           hv_store(pair, "spec_raw", 8, newSVpv(spec_line, 0), 0);
111 12           hv_store(pair, "content_raw", 11, newSVpv(content_line, 0), 0);
112            
113 12           AV *spec_fields = newAV();
114 12           bool line_repeat = false;
115            
116 12           char *line_end = spec_line + strlen(spec_line) - 1;
117 12 50         while (line_end > spec_line && (*line_end == ' ' || *line_end == '\t')) line_end--;
    50          
    50          
118 12 50         if (line_end > spec_line && *line_end == '~' && *(line_end - 1) == '~') {
    100          
    50          
119 2           line_repeat = true;
120             }
121            
122 12           char *pos = spec_line;
123 70 100         while (*pos) {
124 58 100         if (*pos == '@') {
125 15           pos++;
126 15 100         if (*pos == '<' || *pos == '>' || *pos == '|' || *pos == '^' || *pos == '*') {
    100          
    50          
    0          
    0          
127 15           HV *field = newHV();
128 15           char type = *pos;
129 15           pos++;
130            
131 161 50         while (*pos && *pos == type) {
    100          
132 146           pos++;
133             }
134            
135 15           int percentage = 0;
136 15 50         if (isdigit(*pos)) {
137 15           percentage = atoi(pos);
138 45 100         while (isdigit(*pos)) pos++;
139             }
140            
141 15           bool word_break = false;
142 15 100         if (*pos == ',') {
143 2           word_break = true;
144 2           pos++;
145             }
146            
147 15           bool connect_next = false;
148 15 50         if (*pos == '.') {
149 0           connect_next = true;
150 0           pos++;
151             }
152            
153 15           bool repeat_field = false;
154 15 100         if (*pos == '~' && *(pos + 1) == '~') {
    50          
155 4           char *check_end = pos + 2;
156 8 50         while (*check_end && (*check_end == ' ' || *check_end == '\t')) check_end++;
    100          
    50          
157 4 50         if (*check_end == '\0') {
158 0           line_repeat = true;
159             } else {
160 4           repeat_field = true;
161             }
162 4           pos += 2;
163             }
164            
165 15           hv_store(field, "type", 4, newSVpvf("%c", type), 0);
166 15           hv_store(field, "percentage", 10, newSViv(percentage), 0);
167 15           hv_store(field, "repeat_field", 12, newSViv(repeat_field), 0);
168 15           hv_store(field, "connect_next", 12, newSViv(connect_next), 0);
169 15           hv_store(field, "word_break", 10, newSViv(word_break), 0);
170 15           hv_store(field, "has_at_prefix", 13, newSViv(1), 0);
171            
172 15           av_push(spec_fields, newRV_noinc((SV*)field));
173             }
174 60 100         } else if (*pos == '<' || *pos == '>' || *pos == '|' || *pos == '^' || *pos == '*') {
    100          
    100          
    100          
    100          
175 17           HV *field = newHV();
176 17           char type = *pos;
177 17           pos++;
178            
179 17 100         if (type == '^') {
180 80 50         while (*pos && (*pos == '^' || *pos == '<')) {
    50          
    100          
181 77           pos++;
182             }
183             } else {
184 60 50         while (*pos && *pos == type) {
    100          
185 46           pos++;
186             }
187             }
188            
189 17           int percentage = 0;
190 17 50         if (isdigit(*pos)) {
191 17           percentage = atoi(pos);
192 52 100         while (isdigit(*pos)) pos++;
193             }
194            
195 17           bool word_break = false;
196 17 50         if (*pos == ',') {
197 0           word_break = true;
198 0           pos++;
199             }
200            
201 17           bool connect_next = false;
202 17 100         if (*pos == '.') {
203 4           connect_next = true;
204 4           pos++;
205             }
206            
207 17           bool repeat_field = false;
208 17 50         if (*pos == '~' && *(pos + 1) == '~') {
    0          
209 0           char *check_end = pos + 2;
210 0 0         while (*check_end && (*check_end == ' ' || *check_end == '\t')) check_end++;
    0          
    0          
211 0 0         if (*check_end == '\0') {
212 0           line_repeat = true;
213             } else {
214 0           repeat_field = true;
215             }
216 0           pos += 2;
217             }
218            
219 17           hv_store(field, "type", 4, newSVpvf("%c", type), 0);
220 17           hv_store(field, "percentage", 10, newSViv(percentage), 0);
221 17           hv_store(field, "repeat_field", 12, newSViv(repeat_field), 0);
222 17           hv_store(field, "connect_next", 12, newSViv(connect_next), 0);
223 17           hv_store(field, "has_at_prefix", 13, newSViv(0), 0);
224 17           hv_store(field, "word_break", 10, newSViv(word_break), 0);
225            
226 17           av_push(spec_fields, newRV_noinc((SV*)field));
227             } else {
228 26           pos++;
229             }
230             }
231            
232 12           hv_store(pair, "repeat_line", 11, newSViv(line_repeat), 0);
233            
234 12           AV *content_elements = newAV();
235 12           pos = content_line;
236            
237 12           bool has_variables = false;
238 12           char *check_pos = content_line;
239 59 100         while (*check_pos) {
240 54 100         if (*check_pos == '$' || *check_pos == '&' || *check_pos == '@') {
    50          
    100          
241 7           has_variables = true;
242 7           break;
243             }
244 47           check_pos++;
245             }
246            
247 12 100         if (!has_variables) {
248 5           HV *element = newHV();
249 5           hv_store(element, "type", 4, newSVpv("text", 0), 0);
250 5           hv_store(element, "content", 7, newSVpv(content_line, 0), 0);
251 5           av_push(content_elements, newRV_noinc((SV*)element));
252             } else {
253 27 100         while (*pos) {
254 20 50         if (*pos == '&') {
255 0           pos++;
256 0           char *name_start = pos;
257 0 0         while (*pos && (isalnum(*pos) || *pos == '_')) pos++;
    0          
    0          
258 0 0         if (pos > name_start) {
259 0           STRLEN name_len = pos - name_start;
260 0           char *name = (char*)malloc(name_len + 1);
261 0           strncpy(name, name_start, name_len);
262 0           name[name_len] = '\0';
263            
264 0           HV *element = newHV();
265 0           hv_store(element, "type", 4, newSVpv("callback", 0), 0);
266 0           hv_store(element, "name", 4, newSVpv(name, 0), 0);
267 0           av_push(content_elements, newRV_noinc((SV*)element));
268 0           free(name);
269             }
270            
271 0 0         if (isspace(*pos)) {
272 0 0         while (*pos && isspace(*pos)) pos++;
    0          
273             }
274 20 100         } else if (*pos == '$') {
275 10           pos++;
276 10           char *key_start = pos;
277 83 100         while (*pos && (isalnum(*pos) || *pos == '_')) pos++;
    100          
    50          
278 10 50         if (pos > key_start) {
279 10           STRLEN key_len = pos - key_start;
280 10           char *key = (char*)malloc(key_len + 1);
281 10           strncpy(key, key_start, key_len);
282 10           key[key_len] = '\0';
283            
284 10           HV *element = newHV();
285 10           hv_store(element, "type", 4, newSVpv("param", 0), 0);
286 10           hv_store(element, "key", 3, newSVpv(key, 0), 0);
287 10           av_push(content_elements, newRV_noinc((SV*)element));
288 10           free(key);
289             }
290            
291 10 100         if (isspace(*pos)) {
292 61 50         while (*pos && isspace(*pos)) pos++;
    100          
293             }
294 10 100         } else if (*pos == '@') {
295 2           pos++;
296            
297 2           char *prefix = NULL;
298 2 50         if (*pos == '[') {
299 0           pos++;
300 0           char *bracket_start = pos;
301 0 0         while (*pos && *pos != ']') pos++;
    0          
302 0 0         if (*pos == ']') {
303 0           STRLEN bracket_len = pos - bracket_start;
304 0           prefix = (char*)malloc(bracket_len + 1);
305 0           strncpy(prefix, bracket_start, bracket_len);
306 0           prefix[bracket_len] = '\0';
307 0           pos++;
308             }
309             }
310            
311 2           char *array_start = pos;
312 13 50         while (*pos && (isalnum(*pos) || *pos == '_')) pos++;
    100          
    50          
313            
314 2 50         if (pos > array_start) {
315 2           STRLEN array_len = pos - array_start;
316 2           char *array_name = (char*)malloc(array_len + 1);
317 2           strncpy(array_name, array_start, array_len);
318 2           array_name[array_len] = '\0';
319            
320 2           HV *element = newHV();
321 2           hv_store(element, "type", 4, newSVpv("array", 0), 0);
322 2           hv_store(element, "name", 4, newSVpv(array_name, 0), 0);
323            
324 2 50         if (prefix) {
325 0           hv_store(element, "prefix", 6, newSVpv(prefix, 0), 0);
326 0           free(prefix);
327             }
328            
329 2 50         if (*pos == '[') {
330 2           pos++;
331 2           char *bracket_start = pos;
332 4 50         while (*pos && *pos != ']') pos++;
    100          
333 2 50         if (*pos == ']') {
334 2           STRLEN bracket_len = pos - bracket_start;
335 2           char *join_char = (char*)malloc(bracket_len + 1);
336 2           strncpy(join_char, bracket_start, bracket_len);
337 2           join_char[bracket_len] = '\0';
338            
339 2           hv_store(element, "join_char", 9, newSVpv(join_char, 0), 0);
340 2           free(join_char);
341 2           pos++;
342             }
343             }
344            
345 2           av_push(content_elements, newRV_noinc((SV*)element));
346 2           free(array_name);
347             }
348            
349 2 50         if (isspace(*pos)) {
350 4 50         while (*pos && isspace(*pos)) pos++;
    100          
351             }
352             } else {
353 8 50         if (*pos == '[') {
354 0           char *bracket_start = pos + 1;
355 0           char *bracket_end = bracket_start;
356 0 0         while (*bracket_end && *bracket_end != ']') bracket_end++;
    0          
357            
358 0 0         if (*bracket_end == ']' && *(bracket_end + 1) == '@') {
    0          
359 0           STRLEN prefix_len = bracket_end - bracket_start;
360 0           char *prefix = (char*)malloc(prefix_len + 1);
361 0           strncpy(prefix, bracket_start, prefix_len);
362 0           prefix[prefix_len] = '\0';
363            
364 0           pos = bracket_end + 2;
365            
366 0           char *array_start = pos;
367 0 0         while (*pos && (isalnum(*pos) || *pos == '_')) pos++;
    0          
    0          
368            
369 0 0         if (pos > array_start) {
370 0           STRLEN array_len = pos - array_start;
371 0           char *array_name = (char*)malloc(array_len + 1);
372 0           strncpy(array_name, array_start, array_len);
373 0           array_name[array_len] = '\0';
374            
375 0           HV *element = newHV();
376 0           hv_store(element, "type", 4, newSVpv("array", 0), 0);
377 0           hv_store(element, "name", 4, newSVpv(array_name, 0), 0);
378 0           hv_store(element, "prefix", 6, newSVpv(prefix, 0), 0);
379            
380 0 0         if (*pos == '[') {
381 0           pos++;
382 0           char *join_start = pos;
383 0 0         while (*pos && *pos != ']') pos++;
    0          
384 0 0         if (*pos == ']') {
385 0           STRLEN join_len = pos - join_start;
386 0           char *join_char = (char*)malloc(join_len + 1);
387 0           strncpy(join_char, join_start, join_len);
388 0           join_char[join_len] = '\0';
389            
390 0           hv_store(element, "join_char", 9, newSVpv(join_char, 0), 0);
391 0           free(join_char);
392 0           pos++;
393             }
394             }
395            
396 0           av_push(content_elements, newRV_noinc((SV*)element));
397 0           free(array_name);
398             }
399 0           free(prefix);
400            
401 0 0         if (isspace(*pos)) {
402 0 0         while (*pos && isspace(*pos)) pos++;
    0          
403             }
404 0           continue;
405             }
406             }
407            
408 8           char *text_start = pos;
409 48 100         while (*pos && *pos != '&' && *pos != '$' && *pos != '@' &&
    50          
    100          
    100          
410 40 50         !(*pos == '[' && strchr(pos, ']') && strchr(pos, '@'))) pos++;
    0          
    0          
411            
412 8 50         if (pos > text_start) {
413 8           STRLEN text_len = pos - text_start;
414 8           char *text = (char*)malloc(text_len + 1);
415 8           strncpy(text, text_start, text_len);
416 8           text[text_len] = '\0';
417            
418 8           char *trimmed = text;
419 8 50         while (*trimmed && isspace(*trimmed)) trimmed++;
    50          
420 8           char *end = text + strlen(text) - 1;
421 16 100         while (end > text && isspace(*end)) {
    100          
422 8           *end = '\0';
423 8           end--;
424             }
425            
426 8 50         if (strlen(trimmed) > 0) {
427 8           HV *element = newHV();
428 8           hv_store(element, "type", 4, newSVpv("text", 0), 0);
429 8           hv_store(element, "content", 7, newSVpv(text, 0), 0);
430 8           av_push(content_elements, newRV_noinc((SV*)element));
431             }
432 8           free(text);
433             }
434             }
435             }
436             }
437            
438 12           hv_store(pair, "spec_fields", 11, newRV_noinc((SV*)spec_fields), 0);
439 12           hv_store(pair, "content_elements", 16, newRV_noinc((SV*)content_elements), 0);
440 12           av_push(line_pairs, newRV_noinc((SV*)pair));
441            
442 12           i += 2;
443             } else {
444 0           i++;
445             }
446             }
447            
448 25 100         for (i = 0; i < line_count; i++) {
449 24           free(lines[i]);
450             }
451 1           free(lines);
452            
453 1           hv_store(parsed, "line_pairs", 10, newRV_noinc((SV*)line_pairs), 0);
454 1           hv_store(parsed, "total_sections", 14, newSViv(section_num), 0);
455            
456 1           return parsed;
457             }
458              
459 350           char* strip_ansi_codes(const char *str) {
460             dTHX;
461 350           int len = strlen(str);
462 350           char *result = (char*)malloc(len + 1);
463 350           int out_pos = 0;
464 350           int i = 0;
465            
466 17068 100         while (i < len) {
467 16718 100         if (str[i] == '\x1b' && i + 1 < len && str[i+1] == '[') {
    50          
    50          
468 7           i += 2;
469 23 50         while (i < len && str[i] != 'm') {
    100          
470 16           i++;
471             }
472 7 50         if (i < len && str[i] == 'm') {
    50          
473 7           i++;
474             }
475             } else {
476 16711           result[out_pos++] = str[i++];
477             }
478             }
479            
480 350           result[out_pos] = '\0';
481 350           return result;
482             }
483              
484 161           int utf8_display_width(const char *str, int byte_len) {
485             dTHX;
486 161           char *stripped = strip_ansi_codes(str);
487 161           int stripped_len = strlen(stripped);
488            
489 161           int width = 0;
490 161           int i = 0;
491            
492 5488 100         while (i < stripped_len) {
493 5327           unsigned char c = (unsigned char)stripped[i];
494            
495 5327 50         if (c < 0x80) {
496 5327           width++;
497 5327           i++;
498 0 0         } else if (c < 0xC0) {
499 0           i++;
500 0 0         } else if (c < 0xE0) {
501 0           width++;
502 0           i += 2;
503 0 0         } else if (c < 0xF0) {
504 0 0         if (i + 2 < stripped_len) {
505 0           unsigned int codepoint = ((c & 0x0F) << 12) |
506 0           ((stripped[i+1] & 0x3F) << 6) |
507 0           (stripped[i+2] & 0x3F);
508 0 0         width += is_cjk_codepoint(codepoint) ? 2 : 1;
509             } else {
510 0           width++;
511             }
512 0           i += 3;
513 0 0         } else if (c < 0xF8) {
514 0           width++;
515 0           i += 4;
516             } else {
517 0           i++;
518             }
519             }
520            
521 161           free(stripped);
522 161           return width;
523             }
524              
525 11           char* truncate_to_width_word_aware(const char *text, int max_width) {
526             dTHX;
527 11           int byte_len = strlen(text);
528 11           char *stripped = strip_ansi_codes(text);
529 11           int stripped_len = strlen(stripped);
530            
531 11           int current_width = 0;
532 11           int orig_pos = 0;
533 11           int stripped_pos = 0;
534 11           int last_space_orig_pos = -1;
535 11           int last_space_width = 0;
536            
537 280 100         while (orig_pos < byte_len && stripped_pos < stripped_len && current_width < max_width) {
    50          
    100          
538 269 100         if (text[orig_pos] == '\x1b' && orig_pos + 1 < byte_len && text[orig_pos+1] == '[') {
    50          
    50          
539 2           orig_pos += 2;
540 7 50         while (orig_pos < byte_len && text[orig_pos] != 'm') {
    100          
541 5           orig_pos++;
542             }
543 2 50         if (orig_pos < byte_len && text[orig_pos] == 'm') {
    50          
544 2           orig_pos++;
545             }
546 2           continue;
547             }
548            
549 267 100         if (stripped[stripped_pos] == ' ' || stripped[stripped_pos] == '\t') {
    50          
550 53           last_space_orig_pos = orig_pos;
551 53           last_space_width = current_width;
552             }
553            
554 267           unsigned char c = (unsigned char)stripped[stripped_pos];
555 267           int char_width = 1;
556 267           int char_bytes = 1;
557            
558 267 50         if (c < 0x80) {
559 267           char_width = 1;
560 267           char_bytes = 1;
561 0 0         } else if (c < 0xC0) {
562 0           stripped_pos++;
563 0           orig_pos++;
564 0           continue;
565 0 0         } else if (c < 0xE0) {
566 0           char_width = 1;
567 0           char_bytes = 2;
568 0 0         } else if (c < 0xF0) {
569 0 0         if (stripped_pos + 2 < stripped_len) {
570 0           unsigned int codepoint = ((c & 0x0F) << 12) |
571 0           ((stripped[stripped_pos+1] & 0x3F) << 6) |
572 0           (stripped[stripped_pos+2] & 0x3F);
573 0 0         char_width = is_cjk_codepoint(codepoint) ? 2 : 1;
574             } else {
575 0           char_width = 1;
576             }
577 0           char_bytes = 3;
578             } else {
579 0           char_width = 1;
580 0           char_bytes = 4;
581             }
582            
583 267 50         if (current_width + char_width <= max_width) {
584 267           current_width += char_width;
585 267           stripped_pos += char_bytes;
586 267           orig_pos += char_bytes;
587             } else {
588 0           break;
589             }
590             }
591            
592 11 100         if (last_space_orig_pos >= 0 && stripped_pos < stripped_len && current_width >= max_width) {
    50          
    50          
593 5           orig_pos = last_space_orig_pos;
594             }
595            
596 11 100         while (orig_pos < byte_len) {
597 5 50         if (text[orig_pos] == '\x1b' && orig_pos + 1 < byte_len && text[orig_pos+1] == '[') {
    0          
    0          
598 0           orig_pos += 2;
599 0 0         while (orig_pos < byte_len && text[orig_pos] != 'm') {
    0          
600 0           orig_pos++;
601             }
602 0 0         if (orig_pos < byte_len && text[orig_pos] == 'm') {
    0          
603 0           orig_pos++;
604             }
605             } else {
606             break;
607             }
608             }
609            
610 11           char *result = (char*)malloc(orig_pos + 1);
611 11           strncpy(result, text, orig_pos);
612 11           result[orig_pos] = '\0';
613 11           free(stripped);
614 11           return result;
615             }
616              
617 111           char* truncate_to_width(const char *text, int max_width) {
618             dTHX;
619 111           int byte_len = strlen(text);
620 111           char *stripped = strip_ansi_codes(text);
621 111           int stripped_len = strlen(stripped);
622            
623 111           int current_width = 0;
624 111           int orig_pos = 0;
625 111           int stripped_pos = 0;
626            
627 1101 100         while (orig_pos < byte_len && stripped_pos < stripped_len && current_width < max_width) {
    50          
    100          
628 990 50         if (text[orig_pos] == '\x1b' && orig_pos + 1 < byte_len && text[orig_pos+1] == '[') {
    0          
    0          
629 0           orig_pos += 2;
630 0 0         while (orig_pos < byte_len && text[orig_pos] != 'm') {
    0          
631 0           orig_pos++;
632             }
633 0 0         if (orig_pos < byte_len && text[orig_pos] == 'm') {
    0          
634 0           orig_pos++;
635             }
636 0           continue;
637             }
638            
639 990           unsigned char c = (unsigned char)stripped[stripped_pos];
640 990           int char_width = 1;
641 990           int char_bytes = 1;
642            
643 990 50         if (c < 0x80) {
644 990           char_width = 1;
645 990           char_bytes = 1;
646 0 0         } else if (c < 0xC0) {
647 0           stripped_pos++;
648 0           orig_pos++;
649 0           continue;
650 0 0         } else if (c < 0xE0) {
651 0           char_width = 1;
652 0           char_bytes = 2;
653 0 0         } else if (c < 0xF0) {
654 0 0         if (stripped_pos + 2 < stripped_len) {
655 0           unsigned int codepoint = ((c & 0x0F) << 12) |
656 0           ((stripped[stripped_pos+1] & 0x3F) << 6) |
657 0           (stripped[stripped_pos+2] & 0x3F);
658 0 0         char_width = is_cjk_codepoint(codepoint) ? 2 : 1;
659             } else {
660 0           char_width = 1;
661             }
662 0           char_bytes = 3;
663             } else {
664 0           char_width = 1;
665 0           char_bytes = 4;
666             }
667            
668 990 50         if (current_width + char_width <= max_width) {
669 990           current_width += char_width;
670 990           stripped_pos += char_bytes;
671 990           orig_pos += char_bytes;
672             } else {
673 0           break;
674             }
675             }
676            
677 111 100         while (orig_pos < byte_len) {
678 12 50         if (text[orig_pos] == '\x1b' && orig_pos + 1 < byte_len && text[orig_pos+1] == '[') {
    0          
    0          
679 0           orig_pos += 2;
680 0 0         while (orig_pos < byte_len && text[orig_pos] != 'm') {
    0          
681 0           orig_pos++;
682             }
683 0 0         if (orig_pos < byte_len && text[orig_pos] == 'm') {
    0          
684 0           orig_pos++;
685             }
686             } else {
687             break;
688             }
689             }
690            
691 111           char *result = (char*)malloc(orig_pos + 1);
692 111           strncpy(result, text, orig_pos);
693 111           result[orig_pos] = '\0';
694 111           free(stripped);
695 111           return result;
696             }
697              
698 2           char* process_format(SV *self_ref, HV *params) {
699             dTHX;
700            
701 2 50         if (!SvROK(self_ref)) {
702 0           croak("process_format: first argument must be a reference");
703             }
704            
705 2           HV *self = (HV*)SvRV(self_ref);
706 2 50         if (SvTYPE(self) != SVt_PVHV) {
707 0           croak("process_format: first argument must be a hash reference");
708             }
709            
710 2           SV **width_sv = hv_fetch(self, "width", 5, 0);
711 2 50         int max_width = width_sv && *width_sv ? SvIV(*width_sv) : 80;
    50          
712            
713 2           SV **format_sv = hv_fetch(self, "format", 6, 0);
714 2 50         if (!format_sv || !*format_sv || !SvROK(*format_sv)) {
    50          
    50          
715 0           return NULL;
716             }
717            
718 2           HV *format_data = (HV*)SvRV(*format_sv);
719 2           SV **line_pairs_sv = hv_fetch(format_data, "line_pairs", 10, 0);
720 2 50         if (!line_pairs_sv || !*line_pairs_sv || !SvROK(*line_pairs_sv)) {
    50          
    50          
721 0           return NULL;
722             }
723            
724 2           AV *line_pairs = (AV*)SvRV(*line_pairs_sv);
725 2           int num_pairs = av_len(line_pairs) + 1;
726            
727 2           char *result = (char*)malloc(max_width * num_pairs * 10);
728 2           result[0] = '\0';
729 2           int result_len = 0;
730 2           int result_capacity = max_width * num_pairs * 10;
731            
732 2           HV *caller_stash = CopSTASH(PL_curcop);
733 2 50         char *caller_pkg = HvNAME(caller_stash);
    50          
    50          
    0          
    50          
    50          
734            
735 2           HV *mutable_params = newHV();
736             HE *entry;
737 2           hv_iterinit(params);
738 20 100         while ((entry = hv_iternext(params))) {
739             I32 keylen;
740 18           char *key = hv_iterkey(entry, &keylen);
741 18           SV *val = hv_iterval(params, entry);
742 18           hv_store(mutable_params, key, keylen, newSVsv(val), 0);
743             }
744            
745 2           int array_item_idx = 0;
746 2           int row_idx = 0;
747            
748 26 100         for (int pair_idx = 0; pair_idx < num_pairs; pair_idx++) {
749 24           SV **pair_sv = av_fetch(line_pairs, pair_idx, 0);
750 24 50         if (!pair_sv || !*pair_sv || !SvROK(*pair_sv)) continue;
    50          
    50          
751            
752 24           HV *pair = (HV*)SvRV(*pair_sv);
753            
754 24           SV **repeat_line_sv = hv_fetch(pair, "repeat_line", 11, 0);
755 24 50         bool repeat_line = repeat_line_sv && *repeat_line_sv && SvIV(*repeat_line_sv);
    50          
    100          
756            
757 24           SV **spec_fields_sv = hv_fetch(pair, "spec_fields", 11, 0);
758 24           SV **content_elements_sv = hv_fetch(pair, "content_elements", 16, 0);
759            
760 24 50         if (!spec_fields_sv || !*spec_fields_sv || !SvROK(*spec_fields_sv) ||
    50          
    50          
    50          
761 24 50         !content_elements_sv || !*content_elements_sv || !SvROK(*content_elements_sv)) {
    50          
762 0 0         if (mutable_params) SvREFCNT_dec((SV*)mutable_params);
763 0           continue;
764             }
765            
766 24           AV *spec_fields = (AV*)SvRV(*spec_fields_sv);
767 24           AV *content_elements = (AV*)SvRV(*content_elements_sv);
768            
769 24           int num_fields = av_len(spec_fields) + 1;
770 24           int num_elements = av_len(content_elements) + 1;
771            
772 24           bool has_content = true;
773 24           row_idx = 0;
774 24           array_item_idx = 0;
775            
776             typedef struct {
777             char *remaining_content;
778             int field_idx;
779             int element_idx;
780             bool has_remaining;
781             } FieldRepeatState;
782            
783 24           FieldRepeatState *field_repeat_states = (FieldRepeatState*)calloc(num_fields, sizeof(FieldRepeatState));
784 88 100         for (int i = 0; i < num_fields; i++) {
785 64           field_repeat_states[i].remaining_content = NULL;
786 64           field_repeat_states[i].field_idx = i;
787 64           field_repeat_states[i].element_idx = -1;
788 64           field_repeat_states[i].has_remaining = false;
789             }
790            
791 39 50         while (has_content || !repeat_line) {
    0          
792 39           bool any_content_consumed = false;
793 39           bool has_field_repeat_content = false;
794 39           array_item_idx = 0;
795            
796 146 100         for (int i = 0; i < num_fields; i++) {
797 110 100         if (field_repeat_states[i].has_remaining) {
798 3           has_field_repeat_content = true;
799 3           break;
800             }
801             }
802            
803 39           char **element_values = (char**)malloc(num_elements * sizeof(char*));
804 39           int element_idx = 0;
805            
806 118 100         for (int i = 0; i < num_elements; i++) {
807 79           element_values[i] = NULL;
808 79           SV **element_sv = av_fetch(content_elements, i, 0);
809 79 50         if (!element_sv || !*element_sv || !SvROK(*element_sv)) continue;
    50          
    50          
810            
811 79           HV *element = (HV*)SvRV(*element_sv);
812 79           SV **type_sv = hv_fetch(element, "type", 4, 0);
813 79 50         if (!type_sv || !*type_sv) continue;
    50          
814            
815 79           char *type = SvPV_nolen(*type_sv);
816            
817 79 100         if (strcmp(type, "text") == 0) {
818 40           SV **content_sv = hv_fetch(element, "content", 7, 0);
819 40 50         if (content_sv && *content_sv) {
    50          
820 40           element_values[i] = strdup(SvPV_nolen(*content_sv));
821             }
822 39 100         } else if (strcmp(type, "param") == 0) {
823 28           SV **key_sv = hv_fetch(element, "key", 3, 0);
824 28 50         if (key_sv && *key_sv) {
    50          
825 28           char *key = SvPV_nolen(*key_sv);
826 28           SV **param_sv = hv_fetch(mutable_params, key, strlen(key), 0);
827 28 50         if (param_sv && *param_sv) {
    50          
828 28           element_values[i] = strdup(SvPV_nolen(*param_sv));
829             } else {
830 0           element_values[i] = strdup("");
831             }
832             }
833 11 50         } else if (strcmp(type, "callback") == 0) {
834 0           SV **name_sv = hv_fetch(element, "name", 4, 0);
835 0 0         if (name_sv && *name_sv && caller_pkg) {
    0          
    0          
836 0           char *cb_name = SvPV_nolen(*name_sv);
837            
838 0           dSP;
839 0           ENTER;
840 0           SAVETMPS;
841 0 0         PUSHMARK(SP);
842            
843 0 0         XPUSHs(sv_2mortal(newRV_inc((SV*)params)));
844 0           PUTBACK;
845            
846 0           int full_name_len = strlen(caller_pkg) + strlen(cb_name) + 3;
847 0           char *full_name = (char*)malloc(full_name_len);
848 0           snprintf(full_name, full_name_len, "%s::%s", caller_pkg, cb_name);
849            
850 0           int count = call_pv(full_name, G_SCALAR);
851 0           SPAGAIN;
852            
853 0 0         if (count > 0) {
854 0           SV *result_sv = POPs;
855 0 0         if (SvOK(result_sv)) {
856 0           element_values[i] = strdup(SvPV_nolen(result_sv));
857             } else {
858 0           element_values[i] = strdup("");
859             }
860             } else {
861 0           element_values[i] = strdup("");
862             }
863            
864 0           PUTBACK;
865 0 0         FREETMPS;
866 0           LEAVE;
867 0           free(full_name);
868             }
869 11 50         } else if (strcmp(type, "array") == 0) {
870 11 100         if (has_field_repeat_content) {
871 3           element_values[i] = strdup("");
872             } else {
873 8           element_values[i] = strdup("__ARRAY_MARKER__");
874             }
875             }
876            
877 79 50         if (!element_values[i]) {
878 0           element_values[i] = strdup("");
879             }
880             }
881            
882 39           int buffer_size = max_width * 3;
883 39           char *line_buffer = (char*)malloc(buffer_size + 1);
884 39           memset(line_buffer, ' ', max_width);
885 39           line_buffer[max_width] = '\0';
886            
887 39           int current_pos = 0;
888 39           int visual_pos = 0;
889 39           element_idx = 0;
890 39           char *current_join_char = NULL;
891            
892 39           char *array_join_char = NULL;
893 96 100         for (int i = 0; i < num_elements; i++) {
894 68           SV **elem_sv = av_fetch(content_elements, i, 0);
895 68 50         if (elem_sv && *elem_sv && SvROK(*elem_sv)) {
    50          
    50          
896 68           HV *elem = (HV*)SvRV(*elem_sv);
897 68           SV **elem_type_sv = hv_fetch(elem, "type", 4, 0);
898 68 50         if (elem_type_sv && *elem_type_sv && strcmp(SvPV_nolen(*elem_type_sv), "array") == 0) {
    50          
    100          
899 11           SV **join_sv = hv_fetch(elem, "join_char", 9, 0);
900 11 50         if (join_sv && *join_sv) {
    50          
901 11           array_join_char = SvPV_nolen(*join_sv);
902             }
903 11           break;
904             }
905             }
906             }
907            
908 39           int saved_space = 0;
909 161 100         for (int field_idx = 0; field_idx < num_fields; field_idx++) {
910 122           SV **field_sv = av_fetch(spec_fields, field_idx, 0);
911 122 50         if (!field_sv || !*field_sv || !SvROK(*field_sv)) continue;
    50          
    50          
912            
913 122           HV *field = (HV*)SvRV(*field_sv);
914 122           SV **type_sv = hv_fetch(field, "type", 4, 0);
915 122           SV **percentage_sv = hv_fetch(field, "percentage", 10, 0);
916 122           SV **connect_next_sv = hv_fetch(field, "connect_next", 12, 0);
917 122           SV **repeat_field_sv = hv_fetch(field, "repeat_field", 12, 0);
918 122           SV **word_break_sv = hv_fetch(field, "word_break", 10, 0);
919            
920 122 50         if (!type_sv || !*type_sv || !percentage_sv || !*percentage_sv) continue;
    50          
    50          
    50          
921            
922 122           char *field_type = SvPV_nolen(*type_sv);
923 122           int percentage = SvIV(*percentage_sv);
924 122 50         bool connect_next = connect_next_sv && *connect_next_sv && SvIV(*connect_next_sv);
    50          
    100          
925 122 50         bool repeat_field = repeat_field_sv && *repeat_field_sv && SvIV(*repeat_field_sv);
    50          
    100          
926 122 50         bool word_break = word_break_sv && *word_break_sv && SvIV(*word_break_sv);
    50          
    100          
927 122 50         int field_width = percentage > 0 ? (max_width * percentage / 100) : max_width;
928 122           field_width += saved_space;
929 122           saved_space = 0;
930 122 50         if (visual_pos + field_width > max_width) {
931 0           field_width = max_width - visual_pos;
932             }
933            
934 122 50         if (field_width <= 0) continue;
935            
936 122           current_join_char = NULL;
937            
938 122           SV **has_at_sv = hv_fetch(field, "has_at_prefix", 13, 0);
939 122 50         bool has_at_prefix = (has_at_sv && *has_at_sv && SvIV(*has_at_sv));
    50          
    100          
940            
941 122           char *content = "";
942 122           int matched_element_idx = -1;
943 122           bool using_repeat_content = false;
944            
945 122 100         if (field_repeat_states[field_idx].has_remaining) {
946 3           content = field_repeat_states[field_idx].remaining_content;
947 3           matched_element_idx = field_repeat_states[field_idx].element_idx;
948 3           using_repeat_content = true;
949 3           element_idx = matched_element_idx;
950 119 100         } else if (*field_type == '*') {
951 8 50         if (element_idx < num_elements && element_values[element_idx]) {
    50          
952 8           matched_element_idx = element_idx;
953 8           content = element_values[element_idx];
954             }
955 111 100         } else if (*field_type == '^' || has_at_prefix) {
    100          
956 138 50         for (int i = element_idx; i < num_elements; i++) {
957 69           SV **elem_sv = av_fetch(content_elements, i, 0);
958 69 50         if (elem_sv && *elem_sv && SvROK(*elem_sv)) {
    50          
    50          
959 69           HV *elem = (HV*)SvRV(*elem_sv);
960 69           SV **elem_type_sv = hv_fetch(elem, "type", 4, 0);
961 69 50         if (elem_type_sv && *elem_type_sv) {
    50          
962 69           char *elem_type = SvPV_nolen(*elem_type_sv);
963 69 100         if (strcmp(elem_type, "param") == 0 ||
964 41 50         strcmp(elem_type, "callback") == 0) {
965 28           matched_element_idx = i;
966 28           content = element_values[i];
967 28           break;
968 41 50         } else if (strcmp(elem_type, "array") == 0) {
969 41 100         if (has_field_repeat_content && !field_repeat_states[field_idx].has_remaining && array_join_char && field_idx > 0) {
    50          
    50          
    50          
970 9           current_join_char = array_join_char;
971 9           break;
972             }
973 32           SV **name_sv = hv_fetch(elem, "name", 4, 0);
974 32 50         if (name_sv && *name_sv) {
    50          
975 32           char *array_name = SvPV_nolen(*name_sv);
976 32           SV **array_sv = hv_fetch(params, array_name, strlen(array_name), 0);
977            
978 32 50         if (array_sv && *array_sv && SvROK(*array_sv) && SvTYPE(SvRV(*array_sv)) == SVt_PVAV) {
    50          
    50          
    50          
979 32           AV *outer_array = (AV*)SvRV(*array_sv);
980 32           int outer_len = av_len(outer_array) + 1;
981            
982 32 100         int use_row_idx = repeat_line ? row_idx : 0;
983            
984 32 50         if (use_row_idx < outer_len) {
985 32           SV **row_sv = av_fetch(outer_array, use_row_idx, 0);
986            
987 32           AV *row_array = NULL;
988 32           int row_len = 0;
989            
990 32 50         if (row_sv && *row_sv && SvROK(*row_sv) && SvTYPE(SvRV(*row_sv)) == SVt_PVAV) {
    50          
    100          
    50          
991 24           row_array = (AV*)SvRV(*row_sv);
992 24           row_len = av_len(row_array) + 1;
993             } else {
994 8           row_array = outer_array;
995 8           row_len = outer_len;
996 8           use_row_idx = array_item_idx;
997             }
998            
999 32 50         if (array_item_idx < row_len) {
1000 32           SV **item_sv = av_fetch(row_array, array_item_idx, 0);
1001 32 50         if (item_sv && *item_sv) {
    50          
1002 32           SV **prefix_sv = hv_fetch(elem, "prefix", 6, 0);
1003 32           SV **join_sv = hv_fetch(elem, "join_char", 9, 0);
1004 32 50         char *prefix = (prefix_sv && *prefix_sv) ? SvPV_nolen(*prefix_sv) : "";
    0          
1005 32 50         char *join_char = (join_sv && *join_sv) ? SvPV_nolen(*join_sv) : "";
    50          
1006            
1007 32           char *item_str = SvPV_nolen(*item_sv);
1008 32           int content_len = strlen(prefix) + strlen(item_str) + 1;
1009 32           char *array_content = (char*)malloc(content_len);
1010            
1011 32           snprintf(array_content, content_len, "%s%s", prefix, item_str);
1012            
1013 32           matched_element_idx = i;
1014 32           content = array_content;
1015 32 100         current_join_char = (array_item_idx > 0 && strlen(join_char) > 0) ? join_char : NULL;
    50          
1016 32           array_item_idx++;
1017 32           break;
1018             }
1019             }
1020             }
1021             }
1022             }
1023             }
1024             }
1025             }
1026             }
1027             } else {
1028 63 100         for (int i = element_idx; i < num_elements; i++) {
1029 53           SV **elem_sv = av_fetch(content_elements, i, 0);
1030 53 50         if (elem_sv && *elem_sv && SvROK(*elem_sv)) {
    50          
    50          
1031 53           HV *elem = (HV*)SvRV(*elem_sv);
1032 53           SV **elem_type_sv = hv_fetch(elem, "type", 4, 0);
1033 53 50         if (elem_type_sv && *elem_type_sv) {
    50          
1034 53           char *elem_type = SvPV_nolen(*elem_type_sv);
1035 53 100         if (strcmp(elem_type, "text") == 0) {
1036 32           matched_element_idx = i;
1037 32           content = element_values[i];
1038 32           break;
1039             }
1040             }
1041             }
1042             }
1043             }
1044            
1045 122           bool is_mutable_field = (*field_type == '^');
1046            
1047 122 100         char *truncated = word_break ? truncate_to_width_word_aware(content, field_width) : truncate_to_width(content, field_width);
1048 122           int truncated_len = strlen(truncated);
1049 122           int content_width = utf8_display_width(truncated, strlen(truncated));
1050            
1051 122           char *remaining_after_truncate = NULL;
1052 122 100         if (repeat_field) {
1053 36           int skip_bytes = truncated_len;
1054 36           int content_len = strlen(content);
1055            
1056 36 100         if (word_break && skip_bytes < content_len) {
    100          
1057 6 50         while (skip_bytes < content_len && (content[skip_bytes] == ' ' || content[skip_bytes] == '\t')) {
    100          
    50          
1058 3           skip_bytes++;
1059             }
1060             }
1061            
1062 36 100         if (skip_bytes < content_len && strlen(content + skip_bytes) > 0) {
    50          
1063 3           remaining_after_truncate = strdup(content + skip_bytes);
1064             }
1065             }
1066            
1067 122 100         if (using_repeat_content) {
1068 3 50         if (field_repeat_states[field_idx].remaining_content) {
1069 3           free(field_repeat_states[field_idx].remaining_content);
1070 3           field_repeat_states[field_idx].remaining_content = NULL;
1071             }
1072 3           field_repeat_states[field_idx].remaining_content = remaining_after_truncate;
1073 3 100         field_repeat_states[field_idx].has_remaining = (remaining_after_truncate != NULL && strlen(remaining_after_truncate) > 0);
    50          
1074 119 100         } else if (remaining_after_truncate && strlen(remaining_after_truncate) > 0) {
    50          
1075 1           field_repeat_states[field_idx].remaining_content = remaining_after_truncate;
1076 1           field_repeat_states[field_idx].element_idx = matched_element_idx;
1077 1           field_repeat_states[field_idx].has_remaining = true;
1078 118 50         } else if (remaining_after_truncate) {
1079 0           free(remaining_after_truncate);
1080             }
1081            
1082              
1083 122 100         if (is_mutable_field && mutable_params && matched_element_idx >= 0 && matched_element_idx < num_elements) {
    50          
    50          
    50          
1084 14           SV **element_sv = av_fetch(content_elements, matched_element_idx, 0);
1085 14 50         if (element_sv && *element_sv && SvROK(*element_sv)) {
    50          
    50          
1086 14           HV *element = (HV*)SvRV(*element_sv);
1087 14           SV **type_sv = hv_fetch(element, "type", 4, 0);
1088 14 50         if (type_sv && *type_sv && strcmp(SvPV_nolen(*type_sv), "param") == 0) {
    50          
    50          
1089 14           SV **key_sv = hv_fetch(element, "key", 3, 0);
1090 14 50         if (key_sv && *key_sv) {
    50          
1091 14           char *key = SvPV_nolen(*key_sv);
1092 14           char *stripped_truncated = strip_ansi_codes(truncated);
1093 14           int stripped_truncated_len = strlen(stripped_truncated);
1094 14           free(stripped_truncated);
1095            
1096 14           char *content_stripped = strip_ansi_codes(content);
1097 14           int skip_bytes = 0;
1098 14           int chars_counted = 0;
1099 14           int content_len = strlen(content);
1100            
1101 762 100         while (skip_bytes < content_len && chars_counted < stripped_truncated_len) {
    100          
1102 748 50         if (content[skip_bytes] == '\x1b' && skip_bytes + 1 < content_len && content[skip_bytes+1] == '[') {
    0          
    0          
1103 0           skip_bytes += 2;
1104 0 0         while (skip_bytes < content_len && content[skip_bytes] != 'm') {
    0          
1105 0           skip_bytes++;
1106             }
1107 0 0         if (skip_bytes < content_len && content[skip_bytes] == 'm') {
    0          
1108 0           skip_bytes++;
1109             }
1110 0           continue;
1111             }
1112            
1113 748           unsigned char c = (unsigned char)content[skip_bytes];
1114 748 50         if (c < 0x80) {
1115 748           skip_bytes++;
1116 0 0         } else if (c < 0xC0) {
1117 0           skip_bytes++;
1118 0 0         } else if (c < 0xE0) {
1119 0           skip_bytes += 2;
1120 0 0         } else if (c < 0xF0) {
1121 0           skip_bytes += 3;
1122             } else {
1123 0           skip_bytes += 4;
1124             }
1125 748           chars_counted++;
1126             }
1127            
1128 14           free(content_stripped);
1129            
1130 14           char *new_content = content + skip_bytes;
1131 14           hv_store(mutable_params, key, strlen(key), newSVpv(new_content, 0), 0);
1132 14 50         if (skip_bytes > 0) {
1133 14           any_content_consumed = true;
1134             }
1135             }
1136             }
1137             }
1138             }
1139            
1140 122           int join_offset = 0;
1141 122 100         if (current_join_char && strlen(current_join_char) > 0) {
    50          
1142 33           int join_len = strlen(current_join_char);
1143 33 50         if (current_pos + join_len < max_width) {
1144 33           memcpy(line_buffer + current_pos, current_join_char, join_len);
1145 33           join_offset = join_len;
1146 33 50         if (current_pos + join_len < max_width) {
1147 33           line_buffer[current_pos + join_len] = ' ';
1148 33           join_offset++;
1149             }
1150             }
1151             }
1152            
1153 122 100         if (*field_type == '<') {
1154 55           memcpy(line_buffer + current_pos + join_offset, truncated, truncated_len);
1155 55 100         if (connect_next) {
1156 6           int visual_advance = join_offset + content_width;
1157 6           int byte_advance = join_offset + truncated_len;
1158 6           saved_space = field_width - visual_advance;
1159 6           current_pos += byte_advance;
1160 6           visual_pos += visual_advance;
1161             } else {
1162 49           current_pos += field_width;
1163 49           visual_pos += field_width;
1164             }
1165 67 100         } else if (*field_type == '^') {
1166 14           memcpy(line_buffer + current_pos + join_offset, truncated, truncated_len);
1167 14 50         if (connect_next) {
1168 0           int visual_advance = join_offset + content_width;
1169 0           int byte_advance = join_offset + truncated_len;
1170 0           saved_space = field_width - visual_advance;
1171 0           current_pos += byte_advance;
1172 0           visual_pos += visual_advance;
1173             } else {
1174 14           current_pos += field_width;
1175 14           visual_pos += field_width;
1176             }
1177 53 100         } else if (*field_type == '>') {
1178 24 100         if (connect_next) {
1179 2           int start_pos = current_pos + join_offset + field_width - join_offset - content_width;
1180 2 50         if (start_pos < current_pos + join_offset) start_pos = current_pos + join_offset;
1181 2           memcpy(line_buffer + start_pos, truncated, truncated_len);
1182 2           current_pos += field_width;
1183 2           visual_pos += field_width;
1184             } else {
1185 22           int start_pos = current_pos + join_offset + field_width - join_offset - content_width;
1186 22 50         if (start_pos < current_pos + join_offset) start_pos = current_pos + join_offset;
1187 22           memcpy(line_buffer + start_pos, truncated, truncated_len);
1188 22           current_pos += field_width;
1189 22           visual_pos += field_width;
1190             }
1191 29 100         } else if (*field_type == '|') {
1192 21           int available_width = field_width - join_offset;
1193 21           int left_pad = (available_width - content_width) / 2;
1194 21           int start_pos = current_pos + join_offset + left_pad;
1195            
1196 21 50         if (start_pos < current_pos + join_offset) start_pos = current_pos + join_offset;
1197 21 50         if (start_pos + truncated_len > current_pos + field_width) {
1198 0           start_pos = current_pos + field_width - truncated_len;
1199 0 0         if (start_pos < current_pos + join_offset) start_pos = current_pos + join_offset;
1200             }
1201            
1202 21 50         if (join_offset < field_width) {
1203 21           memset(line_buffer + current_pos + join_offset, ' ', field_width - join_offset);
1204             }
1205 21 100         if (truncated_len > 0 && start_pos + truncated_len <= current_pos + buffer_size) {
    50          
1206 18           memcpy(line_buffer + start_pos, truncated, truncated_len);
1207             }
1208 21           current_pos += field_width;
1209 21           visual_pos += field_width;
1210 8 50         } else if (*field_type == '*') {
1211 8 50         if (strlen(content) > 0) {
1212 8           int orig_content_len = strlen(content);
1213 8           int pos_in_field = 0;
1214            
1215 808 100         while (pos_in_field < field_width) {
1216 800           int chars_to_copy = (field_width - pos_in_field < orig_content_len) ?
1217             field_width - pos_in_field : orig_content_len;
1218 800           memcpy(line_buffer + current_pos + pos_in_field, content, chars_to_copy);
1219 800           pos_in_field += chars_to_copy;
1220             }
1221             }
1222 8           current_pos += field_width;
1223 8           visual_pos += field_width;
1224             } else {
1225 0           memcpy(line_buffer + current_pos, truncated, truncated_len);
1226 0           current_pos += field_width;
1227 0           visual_pos += field_width;
1228             }
1229            
1230 122           free(truncated);
1231            
1232 122 100         if (*field_type != '^' && matched_element_idx >= 0 && !has_field_repeat_content) {
    100          
    100          
1233 80           bool is_array_element = false;
1234 80 50         if (matched_element_idx < num_elements) {
1235 80           SV **elem_sv = av_fetch(content_elements, matched_element_idx, 0);
1236 80 50         if (elem_sv && *elem_sv && SvROK(*elem_sv)) {
    50          
    50          
1237 80           HV *elem = (HV*)SvRV(*elem_sv);
1238 80           SV **elem_type_sv = hv_fetch(elem, "type", 4, 0);
1239 80 50         if (elem_type_sv && *elem_type_sv && strcmp(SvPV_nolen(*elem_type_sv), "array") == 0) {
    50          
    100          
1240 32           is_array_element = true;
1241             }
1242             }
1243             }
1244            
1245 80 100         if (!is_array_element) {
1246 48           element_idx = matched_element_idx + 1;
1247 48           current_join_char = NULL;
1248             }
1249             }
1250             }
1251            
1252 39           char *stripped_line = strip_ansi_codes(line_buffer);
1253 39           int visible_len = utf8_display_width(stripped_line, strlen(stripped_line));
1254 39           free(stripped_line);
1255            
1256 39           int output_bytes = 0;
1257 39           int visible_count = 0;
1258 39           int buf_len = strlen(line_buffer);
1259            
1260 3941 100         while (output_bytes < buf_len && visible_count < max_width) {
    100          
1261 3902 100         if (line_buffer[output_bytes] == '\x1b' && output_bytes + 1 < buf_len && line_buffer[output_bytes+1] == '[') {
    50          
    50          
1262 2           output_bytes += 2;
1263 7 50         while (output_bytes < buf_len && line_buffer[output_bytes] != 'm') {
    100          
1264 5           output_bytes++;
1265             }
1266 2 50         if (output_bytes < buf_len && line_buffer[output_bytes] == 'm') {
    50          
1267 2           output_bytes++;
1268             }
1269 2           continue;
1270             }
1271            
1272 3900           unsigned char c = (unsigned char)line_buffer[output_bytes];
1273 3900 50         if (c < 0x80) {
1274 3900           output_bytes++;
1275 0 0         } else if (c < 0xC0) {
1276 0           output_bytes++;
1277 0 0         } else if (c < 0xE0) {
1278 0           output_bytes += 2;
1279 0 0         } else if (c < 0xF0) {
1280 0           output_bytes += 3;
1281             } else {
1282 0           output_bytes += 4;
1283             }
1284 3900           visible_count++;
1285             }
1286            
1287 39 50         if (result_len + output_bytes + 2 >= result_capacity) {
1288 0           result_capacity *= 2;
1289 0           result = (char*)realloc(result, result_capacity);
1290             }
1291            
1292 39           memcpy(result + result_len, line_buffer, output_bytes);
1293 39           result[result_len + output_bytes] = '\n';
1294 39           result[result_len + output_bytes + 1] = '\0';
1295 39           result_len += output_bytes + 1;
1296            
1297 39           free(line_buffer);
1298 118 100         for (int i = 0; i < num_elements; i++) {
1299 79 50         if (element_values[i]) free(element_values[i]);
1300             }
1301 39           free(element_values);
1302            
1303 39           bool has_field_repeat = false;
1304 146 100         for (int i = 0; i < num_fields; i++) {
1305 110 100         if (field_repeat_states[i].has_remaining) {
1306 3           has_field_repeat = true;
1307 3           break;
1308             }
1309             }
1310            
1311 39 100         if (has_field_repeat) {
1312 3           has_content = true;
1313 3           continue;
1314             }
1315            
1316 36           has_content = false;
1317 36 100         if (repeat_line) {
1318 36 100         for (int i = 0; i < num_elements; i++) {
1319 24           SV **element_sv = av_fetch(content_elements, i, 0);
1320 24 50         if (!element_sv || !*element_sv || !SvROK(*element_sv)) continue;
    50          
    50          
1321            
1322 24           HV *element = (HV*)SvRV(*element_sv);
1323 24           SV **type_sv = hv_fetch(element, "type", 4, 0);
1324 24 50         if (!type_sv || !*type_sv) continue;
    50          
1325            
1326 24           char *type = SvPV_nolen(*type_sv);
1327 24 100         if (strcmp(type, "array") == 0) {
1328 6           SV **name_sv = hv_fetch(element, "name", 4, 0);
1329 6 50         if (name_sv && *name_sv) {
    50          
1330 6           char *array_name = SvPV_nolen(*name_sv);
1331 6           SV **array_sv = hv_fetch(params, array_name, strlen(array_name), 0);
1332 6 50         if (array_sv && *array_sv && SvROK(*array_sv) && SvTYPE(SvRV(*array_sv)) == SVt_PVAV) {
    50          
    50          
    50          
1333 6           AV *outer_array = (AV*)SvRV(*array_sv);
1334 6           int outer_len = av_len(outer_array) + 1;
1335            
1336 6 100         if (row_idx + 1 < outer_len) {
1337 4           has_content = true;
1338 4           row_idx++;
1339 4           array_item_idx = 0;
1340 28 100         for (int j = 0; j < num_fields; j++) {
1341 24 50         if (field_repeat_states[j].remaining_content) {
1342 0           free(field_repeat_states[j].remaining_content);
1343 0           field_repeat_states[j].remaining_content = NULL;
1344             }
1345 24           field_repeat_states[j].has_remaining = false;
1346             }
1347 4           break;
1348             }
1349             }
1350             }
1351             }
1352             }
1353            
1354 16 100         if (!has_content && mutable_params && any_content_consumed) {
    50          
    100          
1355 12 100         for (int i = 0; i < num_elements; i++) {
1356 10           SV **element_sv = av_fetch(content_elements, i, 0);
1357 10 50         if (!element_sv || !*element_sv || !SvROK(*element_sv)) continue;
    50          
    50          
1358            
1359 10           HV *element = (HV*)SvRV(*element_sv);
1360 10           SV **type_sv = hv_fetch(element, "type", 4, 0);
1361 10 50         if (!type_sv || !*type_sv) continue;
    50          
1362            
1363 10           char *type = SvPV_nolen(*type_sv);
1364 10 50         if (strcmp(type, "param") == 0) {
1365 10           SV **key_sv = hv_fetch(element, "key", 3, 0);
1366 10 50         if (key_sv && *key_sv) {
    50          
1367 10           char *key = SvPV_nolen(*key_sv);
1368 10           SV **param_sv = hv_fetch(mutable_params, key, strlen(key), 0);
1369 10 50         if (param_sv && *param_sv && SvOK(*param_sv)) {
    50          
    50          
1370 10           char *remaining = SvPV_nolen(*param_sv);
1371 10 50         if (remaining && strlen(remaining) > 0) {
    100          
1372 8           has_content = true;
1373 8           break;
1374             }
1375             }
1376             }
1377             }
1378             }
1379             }
1380             }
1381            
1382 36 100         if (repeat_line && !has_content) break;
    100          
1383 32 100         if (!repeat_line && !has_field_repeat) break;
    50          
1384            
1385             }
1386            
1387 88 100         for (int i = 0; i < num_fields; i++) {
1388 64 50         if (field_repeat_states[i].remaining_content) {
1389 0           free(field_repeat_states[i].remaining_content);
1390             }
1391             }
1392 24           free(field_repeat_states);
1393            
1394             }
1395            
1396 2 50         if (mutable_params) {
1397 2           SvREFCNT_dec((SV*)mutable_params);
1398             }
1399            
1400 2           return result;
1401             }
1402              
1403             MODULE = Print::Format PACKAGE = Print::Format
1404             PROTOTYPES: DISABLE
1405              
1406             HV*
1407             parse_format(SV *format_sv)
1408             CODE:
1409 0           RETVAL = parse_raw_format(format_sv);
1410             OUTPUT:
1411             RETVAL
1412              
1413             void
1414             form(...)
1415             PROTOTYPE: \[$]$
1416             CODE:
1417 1           SV *format_sv = ST(1);
1418 1           GV *gv = newGVgen("Print::Format");
1419 1 50         if (!GvIOp(gv)) {
1420 1           GvIOp(gv) = newIO();
1421             }
1422 1           HV *format_stash = gv_stashpv("Print::Format", GV_ADD);
1423 1           HV *tied_obj = newHV();
1424 1           hv_store(tied_obj, "format_raw", 10, SvREFCNT_inc(format_sv), 0);
1425 1           hv_store(tied_obj, "handle", 6, (SV*)GvIOp(gv), 0);
1426 1           hv_store(tied_obj, "width", 5, newSViv(100), 0);
1427 1           SV *tied_obj_ref = newRV_noinc((SV*)tied_obj);
1428 1           sv_bless(tied_obj_ref, format_stash);
1429 1           sv_magic((SV*)GvIOp(gv), tied_obj_ref, PERL_MAGIC_tiedscalar, NULL, 0);
1430            
1431 1           HV * format = parse_raw_format(format_sv);
1432 1           hv_store(tied_obj, "format", 6, newRV_noinc((SV*)format), 0);
1433              
1434 1           SV *rv = newRV_inc((SV*)gv);
1435 1           SV *self_ref = ST(0);
1436 1 50         if (SvROK(self_ref)) {
1437 1           SV *self = SvRV(self_ref);
1438 1           sv_setsv(self, rv);
1439             }
1440 1           XSRETURN(1);
1441              
1442             int
1443             OPEN(SV *self_ref, SV *type, SV *file, SV *width)
1444             CODE:
1445 1 50         if (!SvROK(self_ref)) {
1446 0           croak("OPEN: first argument must be a reference");
1447             }
1448 1           HV *self = (HV*)SvRV(self_ref);
1449 1 50         if (SvTYPE(self) != SVt_PVHV) {
1450 0           croak("OPEN: first argument must be a hash reference");
1451             }
1452 1           hv_store(self, "type", 4, newSVsv(type), 0);
1453 1           hv_store(self, "fh", 2, newSVsv(file), 0);
1454 1           hv_store(self, "width", 5, newSVsv(width), 0);
1455 1           SV **handle_sv = hv_fetch(self, "handle", 6, 0);
1456 1           SV **fh_sv = hv_fetch(self, "fh", 2, 0);
1457 1           SvREFCNT_inc(self);
1458 1 50         if (fh_sv && *fh_sv && SvOK(*fh_sv)) {
    50          
    50          
1459 1 50         if (SvPOK(*fh_sv)) {
1460 1           char *fh_name = SvPV_nolen(*fh_sv);
1461 1 50         if (strcmp(fh_name, "STDOUT") == 0 && handle_sv && *handle_sv) {
    50          
    50          
1462 1           GV * out = gv_fetchpv("main::STDOUT", GV_ADD, SVt_PVIO);
1463 1 50         if (GvIOp(out)) {
1464 1           hv_store(self, "original_io", 11, newRV_inc((SV*)GvIOp(out)), 0);
1465             }
1466 1           IO *io = (IO*)*handle_sv;
1467 1           GvIOp(out) = io;
1468 0 0         } else if (strcmp(fh_name, "STDERR") == 0 && handle_sv && *handle_sv) {
    0          
    0          
1469 0           GV * err = gv_fetchpv("main::STDERR", GV_ADD, SVt_PVIO);
1470 0 0         if (GvIOp(err)) {
1471 0           hv_store(self, "original_io", 11, newRV_inc((SV*)GvIOp(err)), 0);
1472             }
1473 0           IO *io = (IO*)*handle_sv;
1474 0           GvIOp(err) = io;
1475             } else {
1476 0           PerlIO *fh = PerlIO_open(fh_name, "w");
1477 0 0         if (!fh) {
1478 0           croak("OPEN: failed to open file: %s", fh_name);
1479             }
1480 0 0         if (handle_sv && *handle_sv) {
    0          
1481 0           IO *io = (IO*)*handle_sv;
1482 0           IoOFP(io) = fh;
1483 0           IoIFP(io) = fh;
1484             }
1485 0 0         SV *fh_sv_new = newRV_noinc((SV*)handle_sv && *handle_sv ? (SV*)*handle_sv : newSV(0));
    0          
1486 0           hv_store(self, "fh", 2, fh_sv_new, 0);
1487             }
1488             }
1489             }
1490 1 50         RETVAL = 1;
1491             OUTPUT:
1492             RETVAL
1493              
1494             void
1495             PRINT(SV *self_ref, ...)
1496             CODE:
1497 2 50         if (!SvROK(self_ref)) {
1498 0           croak("PRINT: first argument must be a reference");
1499             }
1500 2           HV *self = (HV*)SvRV(self_ref);
1501 2 50         if (SvTYPE(self) != SVt_PVHV) {
1502 0           croak("PRINT: first argument must be a hash reference");
1503             }
1504 2           SV **fh_sv = hv_fetch(self, "fh", 2, 0);
1505 2 50         if (!fh_sv || !*fh_sv) {
    50          
1506 0           croak("PRINT: no file handle found");
1507             }
1508 2           HV * params = newHV();
1509             int i;
1510 20 100         for (i = 1; i < items; i += 2) {
1511 18 50         if (i + 1 >= items) {
1512 0           croak("PRINT: odd number of arguments in key/value list");
1513             }
1514             STRLEN key_len;
1515 18           char *key = SvPV(ST(i), key_len);
1516 18           hv_store(params, key, key_len, newSVsv(ST(i + 1)), 0);
1517             }
1518              
1519 2           char *to_print = process_format(self_ref, params);
1520 2 50         if (to_print) {
1521 2 50         if (SvPOK(*fh_sv)) {
1522 2 50         if (strcmp(SvPV_nolen(*fh_sv), "STDOUT") == 0) {
1523 2           PerlIO *out = PerlIO_stdout();
1524 2           PerlIO_printf(out, "%s", to_print);
1525 0 0         } else if (strcmp(SvPV_nolen(*fh_sv), "STDERR") == 0) {
1526 0           PerlIO *err = PerlIO_stderr();
1527 0           PerlIO_printf(err, "%s", to_print);
1528             } else {
1529 0           croak("PRINT: unknown file handle name: %s", SvPV_nolen(*fh_sv));
1530             }
1531             } else {
1532 0           SV **handle_sv = hv_fetch(self, "handle", 6, 0);
1533 0 0         if (handle_sv && *handle_sv) {
    0          
1534 0           IO *io = (IO*)*handle_sv;
1535 0           PerlIO *fh = IoOFP(io);
1536 0 0         if (fh) {
1537 0           PerlIO_printf(fh, "%s", to_print);
1538             } else {
1539 0           croak("PRINT: file handle not opened");
1540             }
1541             } else {
1542 0           croak("PRINT: no IO handle found");
1543             }
1544             }
1545 2           free(to_print);
1546             }
1547              
1548             void
1549             CLOSE(SV *self_ref)
1550             CODE:
1551 1           dSP;
1552 1           ENTER;
1553 1           SAVETMPS;
1554 1 50         PUSHMARK(SP);
1555 1 50         XPUSHs(self_ref);
1556 1           PUTBACK;
1557 1           call_method("DESTROY", G_DISCARD);
1558 1 50         FREETMPS;
1559 1           LEAVE;
1560              
1561             void
1562             DESTROY(SV *self_ref)
1563             CODE:
1564 2 50         if (!SvROK(self_ref)) {
1565 0           croak("DESTROY: first argument must be a reference");
1566             }
1567 2           HV *self = (HV*)SvRV(self_ref);
1568 2 50         if (SvTYPE(self) != SVt_PVHV) {
1569 0           croak("DESTROY: first argument must be a hash reference");
1570             }
1571 2           SV **fh_sv = hv_fetch(self, "fh", 2, 0);
1572 2 50         if (fh_sv && *fh_sv && SvOK(*fh_sv)) {
    50          
    50          
1573 2 50         if (SvPOK(*fh_sv)) {
1574 2           char *fh_name = SvPV_nolen(*fh_sv);
1575 2 50         if (strcmp(fh_name, "STDOUT") == 0) {
1576 2           SV **orig_handle_sv = hv_fetch(self, "original_io", 11, 0);
1577 2 50         if (orig_handle_sv && *orig_handle_sv && SvROK(*orig_handle_sv)) {
    50          
    100          
1578 1           GV *out = gv_fetchpv("main::STDOUT", GV_ADD, SVt_PVIO);
1579 1           IO *orig_io = (IO*)SvRV(*orig_handle_sv);
1580 1           GvIOp(out) = orig_io;
1581             }
1582 0 0         } else if (strcmp(fh_name, "STDERR") == 0) {
1583 0           SV **orig_handle_sv = hv_fetch(self, "original_io", 11, 0);
1584 0 0         if (orig_handle_sv && *orig_handle_sv && SvROK(*orig_handle_sv)) {
    0          
    0          
1585 0           GV *err = gv_fetchpv("main::STDERR", GV_ADD, SVt_PVIO);
1586 0           IO *orig_io = (IO*)SvRV(*orig_handle_sv);
1587 0           GvIOp(err) = orig_io;
1588             }
1589             }
1590             } else {
1591 0           SV **handle_sv = hv_fetch(self, "handle", 6, 0);
1592 0 0         if (handle_sv && *handle_sv) {
    0          
1593 0           IO *io = (IO*)*handle_sv;
1594 0           PerlIO *fh = IoOFP(io);
1595 0 0         if (fh) {
1596 0           PerlIO_close(fh);
1597             }
1598             }
1599             }
1600             }
1601              
1602             void
1603             import(...)
1604             CODE:
1605 2 50         char *pkg = HvNAME((HV*)CopSTASH(PL_curcop));
    50          
    50          
    0          
    50          
    50          
1606 2           int pkg_len = strlen(pkg);
1607             STRLEN retlen;
1608 2           int i = 1;
1609 3 100         for (i = 1; i < items; i++) {
1610 1           char * ex = SvPV(ST(i), retlen);
1611 1 50         if (strcmp(ex, "all") == 0) {
1612 0           export_proto(XS_Print__Format_form, pkg, pkg_len, "form", 4, "\\[$]$");
1613 1 50         } else if (strcmp(ex, "form") == 0) {
1614 1           export_proto(XS_Print__Format_form, pkg, pkg_len, "form", 4, "\\[$]$");
1615             } else {
1616 0           croak("Unknown import: %s", ex);
1617             }
1618             }