• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 29651119742

18 Jul 2026 02:41PM UTC coverage: 85.03% (-0.3%) from 85.292%
29651119742

push

github

saitoha
fix: clip size fill for transparent offset bands

79318 of 144774 branches covered (54.79%)

4 of 6 new or added lines in 1 file covered. (66.67%)

3156 existing lines in 22 files now uncovered.

141353 of 166238 relevant lines covered (85.03%)

6269019.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.56
/src/mapfile.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2026 libsixel developers. See `AUTHORS`.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24

25
#if defined(HAVE_CONFIG_H)
26
#include "config.h"
27
#endif
28

29
#if HAVE_CTYPE_H
30
#include <ctype.h>
31
#endif  /* HAVE_CTYPE_H */
32
#if HAVE_ERRNO_H
33
#include <errno.h>
34
#endif  /* HAVE_ERRNO_H */
35
#if HAVE_LIMITS_H
36
#include <limits.h>
37
#endif  /* HAVE_LIMITS_H */
38
#if HAVE_STDIO_H
39
#include <stdio.h>
40
#endif  /* HAVE_STDIO_H */
41
#if HAVE_STDLIB_H
42
#include <stdlib.h>
43
#endif  /* HAVE_STDLIB_H */
44
#if HAVE_STDINT_H
45
#include <stdint.h>
46
#endif  /* HAVE_STDINT_H */
47
#if HAVE_STRING_H
48
#include <string.h>
49
#endif  /* HAVE_STRING_H */
50
#if HAVE_SYS_STAT_H
51
#include <sys/stat.h>
52
#endif  /* HAVE_SYS_STAT_H */
53

54
/* Keep SIZE_MAX available even on strict C99 environments. */
55
#ifndef SIZE_MAX
56
#define SIZE_MAX ((size_t)-1)
57
#endif
58

59
/* Keep palette mapfile reads bounded to avoid excessive memory growth. */
60
#define SIXEL_MAPFILE_READ_MAX_BYTES (16u * 1024u * 1024u)
61

62
#include <sixel.h>
63

64
#include "compat_stub.h"
65
#include "dither.h"
66
#include "encoder.h"
67
#include "mapfile.h"
68

69
static int
70
sixel_path_has_extension(char const *path, char const *extension)
8,332✔
71
{
72
    size_t path_len;
3,719✔
73
    size_t ext_len;
3,719✔
74
    size_t index;
3,719✔
75

76
    path_len = 0u;
8,332✔
77
    ext_len = 0u;
8,332✔
78
    index = 0u;
8,332✔
79

80
    if (path == NULL || extension == NULL) {
8,332!
81
        return 0;
82
    }
83

84
    path_len = strlen(path);
8,332✔
85
    ext_len = strlen(extension);
8,332✔
86
    if (ext_len == 0u || path_len < ext_len) {
8,332!
87
        return 0;
1,461✔
88
    }
89

90
    for (index = 0u; index < ext_len; ++index) {
16,416✔
91
        unsigned char path_ch;
6,619✔
92
        unsigned char ext_ch;
6,619✔
93

94
        path_ch = (unsigned char)path[path_len - ext_len + index];
14,702✔
95
        ext_ch = (unsigned char)extension[index];
14,702✔
96
        if (tolower(path_ch) != tolower(ext_ch)) {
14,702✔
97
            return 0;
2,216✔
98
        }
99
    }
755✔
100

101
    return 1;
936✔
102
}
565✔
103

104

105
/*
106
 * Palette specification parser
107
 *
108
 *   TYPE:PATH  -> explicit format prefix
109
 *   PATH       -> rely on extension or heuristics
110
 *
111
 * The ASCII diagram below shows how the prefix is peeled:
112
 *
113
 *   [type] : [path]
114
 *    ^-- left part selects decoder/encoder when present.
115
 */
116
char const *
117
sixel_palette_strip_prefix(char const *spec,
5,400✔
118
                           sixel_palette_format_t *format_hint)
119
{
120
    char const *colon;
2,395✔
121
    size_t type_len;
2,395✔
122
    size_t index;
2,395✔
123
    char lowered[16];
2,395✔
124

125
    colon = NULL;
5,400✔
126
    type_len = 0u;
5,400✔
127
    index = 0u;
5,400✔
128

129
    if (format_hint != NULL) {
5,400✔
130
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
3,288✔
131
    }
225✔
132
    if (spec == NULL) {
5,400✔
133
        return NULL;
134
    }
135

136
    colon = strchr(spec, ':');
5,400✔
137
    if (colon == NULL) {
5,400✔
138
        return spec;
564✔
139
    }
140

141
    type_len = (size_t)(colon - spec);
4,398✔
142
    if (type_len == 0u || type_len >= sizeof(lowered)) {
4,398!
143
        return spec;
144
    }
145

146
    for (index = 0u; index < type_len; ++index) {
20,464✔
147
        lowered[index] = (char)tolower((unsigned char)spec[index]);
16,066✔
148
    }
1,159✔
149
    lowered[type_len] = '\0';
4,398✔
150

151
    if (strcmp(lowered, "act") == 0) {
4,398✔
152
        if (format_hint != NULL) {
132✔
153
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
68✔
154
        }
5✔
155
        return colon + 1;
132✔
156
    }
157
    if (strcmp(lowered, "pal") == 0) {
4,266✔
158
        if (format_hint != NULL) {
992✔
159
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
752✔
160
        }
47✔
161
        return colon + 1;
992✔
162
    }
163
    if (strcmp(lowered, "pal-jasc") == 0) {
3,274✔
164
        if (format_hint != NULL) {
494✔
165
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
302✔
166
        }
23✔
167
        return colon + 1;
494✔
168
    }
169
    if (strcmp(lowered, "pal-riff") == 0) {
2,780✔
170
        if (format_hint != NULL) {
642✔
171
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
402✔
172
        }
30✔
173
        return colon + 1;
642✔
174
    }
175
    if (strcmp(lowered, "gpl") == 0) {
2,138!
176
        if (format_hint != NULL) {
734✔
177
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
462✔
178
        }
33✔
179
        return colon + 1;
734✔
180
    }
181

182
    return spec;
780✔
183
}
357✔
184

185
sixel_palette_format_t
186
sixel_palette_format_from_extension(char const *path)
3,288✔
187
{
188
    if (path == NULL) {
3,288✔
189
        return SIXEL_PALETTE_FORMAT_NONE;
190
    }
191

192
    if (sixel_path_has_extension(path, ".act")) {
3,288✔
193
        return SIXEL_PALETTE_FORMAT_ACT;
119✔
194
    }
195
    if (sixel_path_has_extension(path, ".pal")) {
3,072✔
196
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
600✔
197
    }
198
    if (sixel_path_has_extension(path, ".gpl")) {
1,972✔
199
        return SIXEL_PALETTE_FORMAT_GPL;
369✔
200
    }
201

202
    return SIXEL_PALETTE_FORMAT_NONE;
881✔
203
}
225✔
204

205
int
206
sixel_path_has_any_extension(char const *path)
2,360✔
207
{
208
    char const *slash_forward;
1,065✔
209
#if defined(_WIN32)
210
    char const *slash_backward;
668✔
211
#endif
212
    char const *start;
1,065✔
213
    char const *dot;
1,065✔
214

215
    slash_forward = NULL;
2,360✔
216
#if defined(_WIN32)
217
    slash_backward = NULL;
1,503✔
218
#endif
219
    start = path;
2,360✔
220
    dot = NULL;
2,360✔
221

222
    if (path == NULL) {
2,360✔
223
        return 0;
224
    }
225

226
    slash_forward = strrchr(path, '/');
2,360✔
227
#if defined(_WIN32)
228
    slash_backward = strrchr(path, '\\');
1,503✔
229
    if (slash_backward != NULL &&
1,503✔
230
            (slash_forward == NULL || slash_backward > slash_forward)) {
312✔
231
        slash_forward = slash_backward;
232
    }
233
#endif
234
    if (slash_forward == NULL) {
2,360✔
235
        start = path;
181✔
236
    } else {
21✔
237
        start = slash_forward + 1;
2,036✔
238
    }
239

240
    dot = strrchr(start, '.');
2,360✔
241
    if (dot == NULL) {
2,360✔
242
        return 0;
269✔
243
    }
244

245
    if (dot[1] == '\0') {
1,874!
246
        return 0;
×
247
    }
248

249
    return 1;
1,026✔
250
}
167✔
251

252
static int
253
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
1,530✔
254
{
255
    if (data == NULL || size < 3u) {
1,530!
256
        return 0;
257
    }
258
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
1,530!
259
        return 1;
48✔
260
    }
261
    return 0;
813✔
262
}
108✔
263

264
static int
265
sixel_palette_has_non_utf8_text_bom(unsigned char const *data, size_t size)
1,128✔
266
{
267
    if (data == NULL || size < 2u) {
1,128!
268
        return 0;
269
    }
270
    /*
271
     * Text palettes are UTF-8 only. Reject UTF-16/UTF-32 BOM markers
272
     * explicitly so diagnostics stay deterministic.
273
     */
274
    if (size >= 4u) {
1,128!
275
        if (data[0] == 0xffu && data[1] == 0xfeu
1,128!
276
                && data[2] == 0x00u && data[3] == 0x00u) {
26!
277
            return 1;
278
        }
279
        if (data[0] == 0x00u && data[1] == 0x00u
1,128!
280
                && data[2] == 0xfeu && data[3] == 0xffu) {
×
281
            return 1;
282
        }
283
    }
81✔
284
    if (data[0] == 0xffu && data[1] == 0xfeu) {
1,128!
285
        return 1;
14✔
286
    }
287
    if (data[0] == 0xfeu && data[1] == 0xffu) {
1,102!
288
        return 1;
26✔
289
    }
290
    return 0;
589✔
291
}
81✔
292

293
static int
294
sixel_palette_tail_is_blank(char const *tail)
470✔
295
{
296
    if (tail == NULL) {
470✔
297
        return 0;
298
    }
299

300
    while (*tail == ' ' || *tail == '\t') {
470!
301
        ++tail;
×
302
    }
303

304
    return *tail == '\0';
470✔
305
}
32✔
306

307

308
static int
309
sixel_palette_contains_nul_byte(unsigned char const *data, size_t size)
589✔
310
{
311
    size_t index;
312

313
    index = 0u;
589✔
314

315
    if (data == NULL) {
589!
316
        return 0;
317
    }
318

319
    for (index = 0u; index < size; ++index) {
537,153,062✔
320
        if (data[index] == '\0') {
537,152,038✔
321
            return 1;
28✔
322
        }
323
    }
33,573,206✔
324

325
    return 0;
561✔
326
}
77✔
327

328

329
/*
330
 * Materialize palette bytes from a stream.
331
 *
332
 * The flow looks like:
333
 *
334
 *   stream --> [scratch buffer] --> [resizable heap buffer]
335
 *                  ^ looped read        ^ returned payload
336
 */
337
SIXELSTATUS
338
sixel_palette_read_stream(FILE *stream,
1,880✔
339
                          sixel_allocator_t *allocator,
340
                          unsigned char **pdata,
341
                          size_t *psize)
342
{
343
    SIXELSTATUS status;
855✔
344
    unsigned char *buffer;
855✔
345
    unsigned char *grown;
855✔
346
    size_t capacity;
855✔
347
    size_t used;
855✔
348
    size_t read_bytes;
855✔
349
    size_t needed;
855✔
350
    size_t new_capacity;
855✔
351
    unsigned char scratch[4096];
855✔
352

353
    status = SIXEL_FALSE;
1,880✔
354
    buffer = NULL;
1,880✔
355
    grown = NULL;
1,880✔
356
    capacity = 0u;
1,880✔
357
    used = 0u;
1,880✔
358
    read_bytes = 0u;
1,880✔
359
    needed = 0u;
1,880✔
360
    new_capacity = 0u;
1,880✔
361

362
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
1,880!
363
        sixel_helper_set_additional_message(
×
364
            "sixel_palette_read_stream: invalid argument.");
365
        return SIXEL_BAD_ARGUMENT;
×
366
    }
367

368
    *pdata = NULL;
1,880✔
369
    *psize = 0u;
1,880✔
370

371
    while (1) {
293,577✔
372
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
306,786✔
373
        if (read_bytes == 0u) {
306,786✔
374
            if (ferror(stream)) {
1,838!
375
                sixel_helper_set_additional_message(
×
376
                    "sixel_palette_read_stream: fread() failed.");
377
                status = SIXEL_LIBC_ERROR;
×
378
                goto cleanup;
×
379
            }
380
            break;
1,838✔
381
        }
382

383
        if (used > SIZE_MAX - read_bytes) {
304,948!
384
            sixel_helper_set_additional_message(
×
385
                "sixel_palette_read_stream: size overflow.");
386
            status = SIXEL_BAD_ALLOCATION;
×
387
            goto cleanup;
×
388
        }
389
        needed = used + read_bytes;
304,948✔
390
        /* Hard-cap mapfile payload growth to 16 MiB. */
391
        if (needed > SIXEL_MAPFILE_READ_MAX_BYTES) {
304,948✔
392
            sixel_helper_set_additional_message(
42✔
393
                "sixel_palette_read_stream: palette input exceeds "
394
                "16 MiB limit.");
395
            status = SIXEL_BAD_INPUT;
42✔
396
            goto cleanup;
42✔
397
        }
398

399
        if (needed > capacity) {
304,906✔
400
            new_capacity = capacity;
2,764✔
401
            if (new_capacity == 0u) {
2,764✔
402
                new_capacity = 4096u;
1,844✔
403
            }
134✔
404
            while (needed > new_capacity) {
3,684✔
405
                if (new_capacity > SIZE_MAX / 2u) {
920!
406
                    sixel_helper_set_additional_message(
407
                        "sixel_palette_read_stream: size overflow.");
408
                    status = SIXEL_BAD_ALLOCATION;
409
                    goto cleanup;
410
                }
411
                new_capacity *= 2u;
920✔
412
            }
413

414
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
2,960✔
415
                                                             new_capacity);
196✔
416
            if (grown == NULL) {
2,764!
417
                sixel_helper_set_additional_message(
×
418
                    "sixel_palette_read_stream: allocation failed.");
419
                status = SIXEL_BAD_ALLOCATION;
×
420
                goto cleanup;
×
421
            }
422

423
            if (buffer != NULL) {
2,764✔
424
                memcpy(grown, buffer, used);
920✔
425
                sixel_allocator_free(allocator, buffer);
920✔
426
            }
62✔
427

428
            buffer = grown;
1,516✔
429
            grown = NULL;
1,516✔
430
            capacity = new_capacity;
1,516✔
431
        }
196✔
432

433
        memcpy(buffer + used, scratch, read_bytes);
304,906✔
434
        used += read_bytes;
304,906✔
435
    }
436

437
    *pdata = buffer;
1,838✔
438
    *psize = used;
1,838✔
439
    status = SIXEL_OK;
1,838✔
440
    return status;
1,838✔
441

442
cleanup:
20✔
443
    if (grown != NULL) {
42!
444
        sixel_allocator_free(allocator, grown);
445
    }
446
    if (buffer != NULL) {
42!
447
        sixel_allocator_free(allocator, buffer);
42✔
448
    }
3✔
449
    return status;
23✔
450
}
137✔
451

452

453
SIXELSTATUS
454
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
1,880✔
455
{
456
    int error_value;
855✔
457
    char error_message[256];
855✔
458
    char strerror_buffer[128];
855✔
459
#if HAVE_SYS_STAT_H
460
    struct stat path_stat;
855✔
461
#endif
462

463
    if (pstream == NULL || pclose == NULL || path == NULL) {
1,880!
464
        sixel_helper_set_additional_message(
×
465
            "sixel_palette_open_read: invalid argument.");
466
        return SIXEL_BAD_ARGUMENT;
×
467
    }
468

469
    error_value = 0;
1,880✔
470
    error_message[0] = '\0';
1,880✔
471

472
    if (strcmp(path, "-") == 0) {
1,880✔
473
        *pstream = stdin;
324✔
474
        *pclose = 0;
324✔
475
        return SIXEL_OK;
324✔
476
    }
477

478
#if HAVE_SYS_STAT_H
479
    if (stat(path, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
1,556!
480
        sixel_compat_snprintf(error_message,
×
481
                              sizeof(error_message),
482
                              "sixel_palette_open_read: mapfile \"%s\" "
483
                              "is a directory.",
484
                              path);
485
        sixel_helper_set_additional_message(error_message);
×
486
        return SIXEL_BAD_INPUT;
×
487
    }
488
#endif
489

490
    errno = 0;
1,556✔
491
    *pstream = sixel_compat_fopen(path, "rb");
1,556✔
492
    if (*pstream == NULL) {
1,556!
493
        error_value = errno;
×
494
        sixel_compat_snprintf(error_message,
×
495
                              sizeof(error_message),
496
                              "sixel_palette_open_read: failed to open "
497
                              "\"%s\": %s.",
498
                              path,
499
                              sixel_compat_strerror(error_value,
500
                                                    strerror_buffer,
501
                                                    sizeof(strerror_buffer)));
502
        sixel_helper_set_additional_message(error_message);
×
503
        return SIXEL_LIBC_ERROR;
×
504
    }
505

506
    *pclose = 1;
1,556✔
507
    return SIXEL_OK;
1,556✔
508
}
137✔
509

510

511
void
512
sixel_palette_close_stream(FILE *stream, int close_stream)
1,880✔
513
{
514
    if (close_stream && stream != NULL) {
1,880!
515
        (void) fclose(stream);
1,556✔
516
    }
116✔
517
}
1,880✔
518

519

520
sixel_palette_format_t
521
sixel_palette_guess_format(unsigned char const *data, size_t size)
522✔
522
{
523
    size_t offset;
234✔
524
    size_t data_size;
234✔
525

526
    offset = 0u;
522✔
527
    data_size = size;
522✔
528

529
    if (data == NULL || size == 0u) {
522!
530
        return SIXEL_PALETTE_FORMAT_NONE;
531
    }
532

533
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
522!
534
            && memcmp(data + 8, "PAL ", 4) == 0) {
51!
535
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
9✔
536
    }
537

538
    if (sixel_palette_has_utf8_bom(data, size)) {
506✔
539
        offset = 3u;
16✔
540
        data_size = size - 3u;
16✔
541
    }
1✔
542

543
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
506!
544
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
237✔
545
    }
546
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
78!
547
        return SIXEL_PALETTE_FORMAT_GPL;
548
    }
549
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
78!
550
        return SIXEL_PALETTE_FORMAT_ACT;
52✔
551
    }
552

553
    return SIXEL_PALETTE_FORMAT_NONE;
14✔
554
}
36✔
555

556

557
static unsigned int
558
sixel_palette_read_le16(unsigned char const *ptr)
278✔
559
{
560
    if (ptr == NULL) {
278!
561
        return 0u;
562
    }
563
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
278✔
564
}
20✔
565

566

567
static unsigned int
568
sixel_palette_read_le32(unsigned char const *ptr)
668✔
569
{
570
    if (ptr == NULL) {
668✔
571
        return 0u;
572
    }
573
    return ((unsigned int)ptr[0])
718✔
574
        | ((unsigned int)ptr[1] << 8)
668✔
575
        | ((unsigned int)ptr[2] << 16)
668✔
576
        | ((unsigned int)ptr[3] << 24);
668✔
577
}
50✔
578

579
static SIXELSTATUS
580
sixel_palette_import_dither_entries(sixel_dither_t *dither,
528✔
581
                                    unsigned char const *entries,
582
                                    unsigned int colors)
583
{
584
    sixel_palette_entries_request_t request;
231✔
585

586
    memset(&request, 0, sizeof(request));
528!
587
    if (dither == NULL || dither->palette == NULL ||
528!
588
            dither->palette->vtbl == NULL ||
528!
589
            dither->palette->vtbl->init_entries == NULL) {
528✔
590
        return SIXEL_BAD_ARGUMENT;
591
    }
592

593
    request.entries = entries;
528✔
594
    request.colors = colors;
528✔
595
    request.depth = 3;
528✔
596
    return dither->palette->vtbl->init_entries(dither->palette, &request);
528✔
597
}
33✔
598

599

600
/*
601
 * Adobe Color Table (*.act) reader
602
 *
603
 *   +-----------+---------------------------+
604
 *   | section   | bytes                     |
605
 *   +-----------+---------------------------+
606
 *   | palette   | 256 entries * 3 RGB bytes |
607
 *   | trailer   | optional count/start pair |
608
 *   +-----------+---------------------------+
609
 */
610
SIXELSTATUS
611
sixel_palette_parse_act(unsigned char const *data,
210✔
612
                        size_t size,
613
                        sixel_encoder_t *encoder,
614
                        sixel_dither_t **dither)
615
{
616
    SIXELSTATUS status;
95✔
617
    sixel_dither_t *local;
95✔
618
    unsigned char const *palette_start;
95✔
619
    unsigned char const *trailer;
95✔
620
    int exported_colors;
95✔
621
    int start_index;
95✔
622

623
    status = SIXEL_FALSE;
210✔
624
    local = NULL;
210✔
625
    palette_start = data;
210✔
626
    trailer = NULL;
210✔
627
    exported_colors = 0;
210✔
628
    start_index = 0;
210✔
629

630
    if (encoder == NULL || dither == NULL) {
210!
631
        sixel_helper_set_additional_message(
×
632
            "sixel_palette_parse_act: invalid argument.");
633
        return SIXEL_BAD_ARGUMENT;
×
634
    }
635
    if (data == NULL || size < 256u * 3u) {
210!
636
        sixel_helper_set_additional_message(
26✔
637
            "sixel_palette_parse_act: truncated ACT palette.");
638
        return SIXEL_BAD_INPUT;
26✔
639
    }
640

641
    if (size == 256u * 3u) {
184✔
642
        exported_colors = 256;
9✔
643
        start_index = 0;
9✔
644
    } else if (size == 256u * 3u + 4u) {
169✔
645
        trailer = data + 256u * 3u;
142✔
646
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
152✔
647
                                | (unsigned int)trailer[1]);
142✔
648
        start_index = (int)(((unsigned int)trailer[2] << 8)
152✔
649
                            | (unsigned int)trailer[3]);
142✔
650
    } else {
10✔
651
        sixel_helper_set_additional_message(
26✔
652
            "sixel_palette_parse_act: invalid ACT length.");
653
        return SIXEL_BAD_INPUT;
26✔
654
    }
655

656
    if (start_index < 0 || start_index >= 256) {
151!
657
        sixel_helper_set_additional_message(
26✔
658
            "sixel_palette_parse_act: ACT start index out of range.");
659
        return SIXEL_BAD_INPUT;
26✔
660
    }
661
    /*
662
     * Keep legacy ACT behavior for count 0 (means 256), but reject
663
     * explicit values above the 8-bit palette limit.
664
     */
665
    if (exported_colors <= 0) {
132✔
666
        exported_colors = 256;
9✔
667
    } else if (exported_colors > 256) {
117✔
668
        sixel_helper_set_additional_message(
26✔
669
            "sixel_palette_parse_act: invalid ACT color count.");
670
        return SIXEL_BAD_INPUT;
26✔
671
    }
672
    if (start_index + exported_colors > 256) {
106✔
673
        sixel_helper_set_additional_message(
26✔
674
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
675
        return SIXEL_BAD_INPUT;
26✔
676
    }
677

678
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
80✔
679
    if (SIXEL_FAILED(status)) {
80!
680
        return status;
681
    }
682

683
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
80✔
684
    local->gpu_policy = encoder->gpu_policy;
80✔
685

686
    status = sixel_palette_import_dither_entries(
115✔
687
        local,
5✔
688
        palette_start + (size_t)start_index * 3u,
80✔
689
        (unsigned int)exported_colors);
5✔
690
    if (SIXEL_FAILED(status)) {
80!
691
        sixel_dither_unref(local);
×
UNCOV
692
        return status;
×
693
    }
694

695
    *dither = local;
80✔
696
    return SIXEL_OK;
80✔
697
}
15✔
698

699

700
SIXELSTATUS
701
sixel_palette_parse_pal_jasc(unsigned char const *data,
730✔
702
                             size_t size,
703
                             sixel_encoder_t *encoder,
704
                             sixel_dither_t **dither)
705
{
706
    SIXELSTATUS status;
330✔
707
    char *text;
330✔
708
    size_t index;
330✔
709
    size_t offset;
330✔
710
    char *cursor;
330✔
711
    char *line;
330✔
712
    char *line_end;
330✔
713
    int stage;
330✔
714
    int exported_colors;
330✔
715
    int parsed_colors;
330✔
716
    sixel_dither_t *local;
330✔
717
    unsigned char *palette_buffer;
330✔
718
    long component;
330✔
719
    char *parse_end;
330✔
720
    int value_index;
330✔
721
    int values[3];
330✔
722
    char tail;
330✔
723

724
    status = SIXEL_FALSE;
730✔
725
    text = NULL;
730✔
726
    index = 0u;
730✔
727
    offset = 0u;
730✔
728
    cursor = NULL;
730✔
729
    line = NULL;
730✔
730
    line_end = NULL;
730✔
731
    stage = 0;
730✔
732
    exported_colors = 0;
730✔
733
    parsed_colors = 0;
730✔
734
    local = NULL;
730✔
735
    palette_buffer = NULL;
730✔
736
    component = 0;
730✔
737
    parse_end = NULL;
730✔
738
    value_index = 0;
730✔
739
    values[0] = 0;
730✔
740
    values[1] = 0;
730✔
741
    values[2] = 0;
730✔
742

743
    if (encoder == NULL || dither == NULL) {
730!
UNCOV
744
        sixel_helper_set_additional_message(
×
745
            "sixel_palette_parse_pal_jasc: invalid argument.");
UNCOV
746
        return SIXEL_BAD_ARGUMENT;
×
747
    }
748
    if (data == NULL || size == 0u) {
730!
UNCOV
749
        sixel_helper_set_additional_message(
×
750
            "sixel_palette_parse_pal_jasc: empty palette.");
UNCOV
751
        return SIXEL_BAD_INPUT;
×
752
    }
753
    if (size > SIZE_MAX - 1u) {
730!
UNCOV
754
        sixel_helper_set_additional_message(
×
755
            "sixel_palette_parse_pal_jasc: size overflow.");
UNCOV
756
        return SIXEL_BAD_ALLOCATION;
×
757
    }
758
    if (sixel_palette_has_non_utf8_text_bom(data, size)) {
730✔
759
        sixel_helper_set_additional_message(
26✔
760
            "sixel_palette_parse_pal_jasc: unsupported text encoding.");
761
        return SIXEL_BAD_INPUT;
26✔
762
    }
763
    if (sixel_palette_contains_nul_byte(data, size)) {
704✔
764
        sixel_helper_set_additional_message(
26✔
765
            "sixel_palette_parse_pal_jasc: embedded NUL byte.");
766
        return SIXEL_BAD_INPUT;
26✔
767
    }
768

769
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
678✔
770
    if (text == NULL) {
678!
UNCOV
771
        sixel_helper_set_additional_message(
×
772
            "sixel_palette_parse_pal_jasc: allocation failed.");
UNCOV
773
        return SIXEL_BAD_ALLOCATION;
×
774
    }
775
    memcpy(text, data, size);
678✔
776
    text[size] = '\0';
678✔
777

778
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
678✔
779
        offset = 3u;
16✔
780
    }
1✔
781
    cursor = text + offset;
678✔
782

783
    while (*cursor != '\0') {
4,246✔
784
        line = cursor;
15,149✔
785
        line_end = cursor;
2,169✔
786
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
29,280!
787
            ++line_end;
25,374✔
788
        }
789
        if (*line_end != '\0') {
3,906!
790
            *line_end = '\0';
3,906✔
791
            cursor = line_end + 1;
3,906✔
792
        } else {
261✔
793
            cursor = line_end;
1,044✔
794
        }
795
        while (*cursor == '\n' || *cursor == '\r') {
3,906!
UNCOV
796
            ++cursor;
×
797
        }
798

799
        while (*line == ' ' || *line == '\t') {
3,906!
UNCOV
800
            ++line;
×
801
        }
802
        index = strlen(line);
3,906✔
803
        while (index > 0u) {
3,906!
804
            tail = line[index - 1];
3,906✔
805
            if (tail != ' ' && tail != '\t') {
3,906!
806
                break;
2,169✔
807
            }
808
            line[index - 1] = '\0';
×
UNCOV
809
            --index;
×
810
        }
811
        if (*line == '\0') {
3,906!
UNCOV
812
            continue;
×
813
        }
814
        if (*line == '#') {
3,906!
UNCOV
815
            continue;
×
816
        }
817

818
        if (stage == 0) {
3,906✔
819
            if (strcmp(line, "JASC-PAL") != 0) {
678✔
820
                sixel_helper_set_additional_message(
26✔
821
                    "sixel_palette_parse_pal_jasc: missing header.");
822
                status = SIXEL_BAD_INPUT;
26✔
823
                goto cleanup;
26✔
824
            }
825
            stage = 1;
652✔
826
            continue;
652✔
827
        }
828
        if (stage == 1) {
3,228✔
829
            if (strcmp(line, "0100") != 0) {
652✔
830
                sixel_helper_set_additional_message(
104✔
831
                    "sixel_palette_parse_pal_jasc: invalid version.");
832
                status = SIXEL_BAD_INPUT;
104✔
833
                goto cleanup;
104✔
834
            }
835
            stage = 2;
548✔
836
            continue;
548✔
837
        }
838
        if (stage == 2) {
2,576✔
839
            component = strtol(line, &parse_end, 10);
522✔
840
            /* Reject glued suffixes such as "256x" in strict mode. */
841
            if (parse_end == line || component <= 0L || component > 256L
522!
842
                    || !sixel_palette_tail_is_blank(parse_end)) {
472✔
843
                sixel_helper_set_additional_message(
78✔
844
                    "sixel_palette_parse_pal_jasc: invalid color count.");
845
                status = SIXEL_BAD_INPUT;
78✔
846
                goto cleanup;
78✔
847
            }
848
            exported_colors = (int)component;
444✔
849
            if (exported_colors <= 0) {
444!
850
                sixel_helper_set_additional_message(
851
                    "sixel_palette_parse_pal_jasc: invalid color count.");
852
                status = SIXEL_BAD_INPUT;
853
                goto cleanup;
854
            }
855
            palette_buffer = (unsigned char *)sixel_allocator_malloc(
642✔
856
                encoder->allocator,
30✔
857
                (size_t)exported_colors * 3u);
444✔
858
            if (palette_buffer == NULL) {
444!
UNCOV
859
                sixel_helper_set_additional_message(
×
860
                    "sixel_palette_parse_pal_jasc: allocation failed.");
861
                status = SIXEL_BAD_ALLOCATION;
×
UNCOV
862
                goto cleanup;
×
863
            }
864
            status = sixel_dither_new(&local, exported_colors,
474✔
865
                                      encoder->allocator);
30✔
866
            if (SIXEL_FAILED(status)) {
444!
UNCOV
867
                goto cleanup;
×
868
            }
869
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
444✔
870
            local->gpu_policy = encoder->gpu_policy;
444✔
871
            stage = 3;
444✔
872
            continue;
444✔
873
        }
874

875
        value_index = 0;
1,151✔
876
        while (value_index < 3) {
8,086✔
877
            component = strtol(line, &parse_end, 10);
6,110✔
878
            if (parse_end == line || component < 0L || component > 255L) {
6,110✔
879
                sixel_helper_set_additional_message(
78✔
880
                    "sixel_palette_parse_pal_jasc: invalid component.");
881
                status = SIXEL_BAD_INPUT;
78✔
882
                goto cleanup;
78✔
883
            }
884
            values[value_index] = (int)component;
6,032✔
885
            ++value_index;
6,032✔
886
            line = parse_end;
6,032✔
887
            while (*line == ' ' || *line == '\t') {
10,062!
888
                ++line;
4,030✔
889
            }
890
        }
891
        /* A JASC color row must be exactly 3 numeric components. */
892
        if (*line != '\0') {
1,976✔
893
            sixel_helper_set_additional_message(
26✔
894
                "sixel_palette_parse_pal_jasc: invalid component.");
895
            status = SIXEL_BAD_INPUT;
26✔
896
            goto cleanup;
26✔
897
        }
898

899
        if (parsed_colors >= exported_colors) {
1,950✔
900
            sixel_helper_set_additional_message(
26✔
901
                "sixel_palette_parse_pal_jasc: excess entries.");
902
            status = SIXEL_BAD_INPUT;
26✔
903
            goto cleanup;
26✔
904
        }
905

906
        palette_buffer[parsed_colors * 3 + 0] =
1,924✔
907
            (unsigned char)values[0];
1,924✔
908
        palette_buffer[parsed_colors * 3 + 1] =
1,924✔
909
            (unsigned char)values[1];
1,924✔
910
        palette_buffer[parsed_colors * 3 + 2] =
1,924✔
911
            (unsigned char)values[2];
1,924✔
912
        ++parsed_colors;
1,924✔
913
    }
914

915
    if (stage < 3) {
340✔
916
        sixel_helper_set_additional_message(
26✔
917
            "sixel_palette_parse_pal_jasc: incomplete header.");
918
        status = SIXEL_BAD_INPUT;
26✔
919
        goto cleanup;
26✔
920
    }
921
    if (parsed_colors != exported_colors) {
314✔
922
        sixel_helper_set_additional_message(
26✔
923
            "sixel_palette_parse_pal_jasc: color count mismatch.");
924
        status = SIXEL_BAD_INPUT;
26✔
925
        goto cleanup;
26✔
926
    }
927

928
    status = sixel_palette_import_dither_entries(
288✔
929
        local,
18✔
930
        palette_buffer,
18✔
931
        (unsigned int)exported_colors);
18✔
932
    if (SIXEL_FAILED(status)) {
288!
UNCOV
933
        goto cleanup;
×
934
    }
935

936
    *dither = local;
288✔
937
    status = SIXEL_OK;
288✔
938

939
cleanup:
324✔
940
    if (SIXEL_FAILED(status) && local != NULL) {
686✔
941
        sixel_dither_unref(local);
156✔
942
    }
12✔
943
    if (palette_buffer != NULL) {
678✔
944
        sixel_allocator_free(encoder->allocator, palette_buffer);
444✔
945
    }
30✔
946
    if (text != NULL) {
678!
947
        sixel_allocator_free(encoder->allocator, text);
678✔
948
    }
48✔
949
    return status;
486✔
950
}
52✔
951

952

953
SIXELSTATUS
954
sixel_palette_parse_pal_riff(unsigned char const *data,
386✔
955
                             size_t size,
956
                             sixel_encoder_t *encoder,
957
                             sixel_dither_t **dither)
958
{
959
    SIXELSTATUS status;
177✔
960
    size_t offset;
177✔
961
    size_t chunk_size;
177✔
962
    sixel_dither_t *local;
177✔
963
    unsigned char const *chunk;
177✔
964
    unsigned char *palette_buffer;
177✔
965
    unsigned int entry_count;
177✔
966
    unsigned int version;
177✔
967
    unsigned int index;
177✔
968
    size_t palette_offset;
177✔
969
    size_t remaining;
177✔
970
    size_t chunk_payload_limit;
177✔
971
    size_t chunk_padded_size;
177✔
972
    size_t next_offset;
177✔
973
    unsigned int riff_size_declared;
177✔
974
    size_t riff_size_expected;
177✔
975
    unsigned char const *data_chunk;
177✔
976
    size_t data_chunk_size;
177✔
977

978
    status = SIXEL_FALSE;
386✔
979
    offset = 0u;
386✔
980
    chunk_size = 0u;
386✔
981
    local = NULL;
386✔
982
    chunk = NULL;
386✔
983
    palette_buffer = NULL;
386✔
984
    entry_count = 0u;
386✔
985
    version = 0u;
386✔
986
    index = 0u;
386✔
987
    palette_offset = 0u;
386✔
988
    remaining = 0u;
386✔
989
    chunk_payload_limit = 0u;
386✔
990
    chunk_padded_size = 0u;
386✔
991
    next_offset = 0u;
386✔
992
    riff_size_declared = 0u;
386✔
993
    riff_size_expected = 0u;
386✔
994
    data_chunk = NULL;
386✔
995
    data_chunk_size = 0u;
386✔
996

997
    if (encoder == NULL || dither == NULL) {
386!
998
        sixel_helper_set_additional_message(
×
999
            "sixel_palette_parse_pal_riff: invalid argument.");
UNCOV
1000
        return SIXEL_BAD_ARGUMENT;
×
1001
    }
1002
    if (data == NULL || size < 12u) {
386!
1003
        sixel_helper_set_additional_message(
26✔
1004
            "sixel_palette_parse_pal_riff: truncated palette.");
1005
        return SIXEL_BAD_INPUT;
26✔
1006
    }
1007
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
360!
1008
        sixel_helper_set_additional_message(
26✔
1009
            "sixel_palette_parse_pal_riff: missing RIFF header.");
1010
        return SIXEL_BAD_INPUT;
26✔
1011
    }
1012
    riff_size_declared = sixel_palette_read_le32(data + 4);
334✔
1013
    riff_size_expected = size - 8u;
334✔
1014
    if (riff_size_expected > 0xffffffffu ||
334!
1015
            riff_size_declared != (unsigned int)riff_size_expected) {
334✔
1016
        sixel_helper_set_additional_message(
26✔
1017
            "sixel_palette_parse_pal_riff: RIFF size mismatch.");
1018
        return SIXEL_BAD_INPUT;
26✔
1019
    }
1020

1021
    offset = 12u;
167✔
1022
    /*
1023
     * Keep chunk traversal overflow-safe by checking remaining bytes
1024
     * with subtraction before any additive offset arithmetic.
1025
     */
1026
    while (offset <= size && size - offset >= 8u) {
564!
1027
        remaining = size - offset;
334✔
1028
        chunk = data + offset;
334✔
1029
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
334✔
1030
        chunk_payload_limit = remaining - 8u;
334✔
1031
        if (chunk_size > chunk_payload_limit) {
334✔
1032
            sixel_helper_set_additional_message(
26✔
1033
                "sixel_palette_parse_pal_riff: chunk extends past end.");
1034
            return SIXEL_BAD_INPUT;
26✔
1035
        }
1036
        if (memcmp(chunk, "data", 4) == 0) {
308✔
1037
            if (data_chunk != NULL) {
256✔
1038
                sixel_helper_set_additional_message(
26✔
1039
                    "sixel_palette_parse_pal_riff: duplicate data chunk.");
1040
                return SIXEL_BAD_INPUT;
26✔
1041
            }
1042
            data_chunk = chunk;
125✔
1043
            data_chunk_size = chunk_size;
125✔
1044
        }
17✔
1045
        chunk_padded_size = chunk_size;
282✔
1046
        if ((chunk_padded_size & 1u) != 0u) {
282✔
1047
            if (chunk_padded_size == SIZE_MAX) {
26!
1048
                sixel_helper_set_additional_message(
1049
                    "sixel_palette_parse_pal_riff: size overflow.");
1050
                return SIXEL_BAD_ALLOCATION;
1051
            }
1052
            ++chunk_padded_size;
26✔
1053
        }
2✔
1054
        if (chunk_padded_size > chunk_payload_limit) {
282✔
1055
            sixel_helper_set_additional_message(
26✔
1056
                "sixel_palette_parse_pal_riff: chunk extends past end.");
1057
            return SIXEL_BAD_INPUT;
26✔
1058
        }
1059
        if (offset > SIZE_MAX - 8u - chunk_padded_size) {
256!
1060
            sixel_helper_set_additional_message(
1061
                "sixel_palette_parse_pal_riff: size overflow.");
1062
            return SIXEL_BAD_ALLOCATION;
1063
        }
1064
        next_offset = offset + 8u + chunk_padded_size;
256✔
1065
        offset = next_offset;
256✔
1066
    }
1067
    if (offset != size) {
230✔
1068
        sixel_helper_set_additional_message(
26✔
1069
            "sixel_palette_parse_pal_riff: trailing bytes after chunks.");
1070
        return SIXEL_BAD_INPUT;
26✔
1071
    }
1072

1073
    if (data_chunk == NULL) {
204✔
1074
        sixel_helper_set_additional_message(
26✔
1075
            "sixel_palette_parse_pal_riff: missing data chunk.");
1076
        return SIXEL_BAD_INPUT;
26✔
1077
    }
1078
    chunk = data_chunk;
178✔
1079
    chunk_size = data_chunk_size;
178✔
1080

1081
    if (chunk_size < 4u) {
178✔
1082
        sixel_helper_set_additional_message(
26✔
1083
            "sixel_palette_parse_pal_riff: data chunk too small.");
1084
        return SIXEL_BAD_INPUT;
26✔
1085
    }
1086
    version = sixel_palette_read_le16(chunk + 8);
152✔
1087
    if (version != 0x0300u) {
152✔
1088
        sixel_helper_set_additional_message(
26✔
1089
            "sixel_palette_parse_pal_riff: invalid RIFF palette version.");
1090
        return SIXEL_BAD_INPUT;
26✔
1091
    }
1092
    entry_count = sixel_palette_read_le16(chunk + 10);
126✔
1093
    if (entry_count == 0u || entry_count > 256u) {
126✔
1094
        sixel_helper_set_additional_message(
52✔
1095
            "sixel_palette_parse_pal_riff: invalid entry count.");
1096
        return SIXEL_BAD_INPUT;
52✔
1097
    }
1098
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
74✔
1099
        sixel_helper_set_additional_message(
26✔
1100
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
1101
        return SIXEL_BAD_INPUT;
26✔
1102
    }
1103

1104
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
48✔
1105
    if (SIXEL_FAILED(status)) {
48!
1106
        return status;
1107
    }
1108
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
48✔
1109
    local->gpu_policy = encoder->gpu_policy;
48✔
1110
    palette_buffer = (unsigned char *)sixel_allocator_malloc(
69✔
1111
        encoder->allocator,
3✔
1112
        (size_t)entry_count * 3u);
48✔
1113
    if (palette_buffer == NULL) {
48!
1114
        sixel_helper_set_additional_message(
×
1115
            "sixel_palette_parse_pal_riff: allocation failed.");
UNCOV
1116
        sixel_dither_unref(local);
×
UNCOV
1117
        return SIXEL_BAD_ALLOCATION;
×
1118
    }
1119
    palette_offset = 12u;
1,854✔
1120
    for (index = 0u; index < entry_count; ++index) {
4,176✔
1121
        palette_buffer[index * 3u + 0u] =
4,128✔
1122
            chunk[palette_offset + index * 4u + 0u];
4,128✔
1123
        palette_buffer[index * 3u + 1u] =
4,128✔
1124
            chunk[palette_offset + index * 4u + 1u];
4,128✔
1125
        palette_buffer[index * 3u + 2u] =
4,128✔
1126
            chunk[palette_offset + index * 4u + 2u];
4,128✔
1127
    }
258✔
1128

1129
    status = sixel_palette_import_dither_entries(
48✔
1130
        local,
3✔
1131
        palette_buffer,
3✔
1132
        (unsigned int)entry_count);
3✔
1133
    sixel_allocator_free(encoder->allocator, palette_buffer);
48✔
1134
    palette_buffer = NULL;
48✔
1135
    if (SIXEL_FAILED(status)) {
48!
UNCOV
1136
        sixel_dither_unref(local);
×
UNCOV
1137
        return status;
×
1138
    }
1139

1140
    *dither = local;
48✔
1141
    return SIXEL_OK;
48✔
1142
}
29✔
1143

1144

1145
SIXELSTATUS
1146
sixel_palette_parse_gpl(unsigned char const *data,
398✔
1147
                        size_t size,
1148
                        sixel_encoder_t *encoder,
1149
                        sixel_dither_t **dither)
1150
{
1151
    SIXELSTATUS status;
181✔
1152
    char *text;
181✔
1153
    size_t offset;
181✔
1154
    char *cursor;
181✔
1155
    char *line;
181✔
1156
    char *line_end;
181✔
1157
    size_t index;
181✔
1158
    int header_seen;
181✔
1159
    int parsed_colors;
181✔
1160
    unsigned char palette_bytes[256 * 3];
181✔
1161
    long component;
181✔
1162
    char *parse_end;
181✔
1163
    int value_index;
181✔
1164
    int values[3];
181✔
1165
    sixel_dither_t *local;
181✔
1166
    char tail;
181✔
1167

1168
    status = SIXEL_FALSE;
398✔
1169
    text = NULL;
398✔
1170
    offset = 0u;
398✔
1171
    cursor = NULL;
398✔
1172
    line = NULL;
398✔
1173
    line_end = NULL;
398✔
1174
    index = 0u;
398✔
1175
    header_seen = 0;
398✔
1176
    parsed_colors = 0;
398✔
1177
    component = 0;
398✔
1178
    parse_end = NULL;
398✔
1179
    value_index = 0;
398✔
1180
    values[0] = 0;
398✔
1181
    values[1] = 0;
398✔
1182
    values[2] = 0;
398✔
1183
    local = NULL;
398✔
1184

1185
    if (encoder == NULL || dither == NULL) {
398!
UNCOV
1186
        sixel_helper_set_additional_message(
×
1187
            "sixel_palette_parse_gpl: invalid argument.");
1188
        return SIXEL_BAD_ARGUMENT;
×
1189
    }
1190
    if (data == NULL || size == 0u) {
398!
UNCOV
1191
        sixel_helper_set_additional_message(
×
1192
            "sixel_palette_parse_gpl: empty palette.");
1193
        return SIXEL_BAD_INPUT;
×
1194
    }
1195
    if (size > SIZE_MAX - 1u) {
398!
UNCOV
1196
        sixel_helper_set_additional_message(
×
1197
            "sixel_palette_parse_gpl: size overflow.");
UNCOV
1198
        return SIXEL_BAD_ALLOCATION;
×
1199
    }
1200
    if (sixel_palette_has_non_utf8_text_bom(data, size)) {
398✔
1201
        sixel_helper_set_additional_message(
26✔
1202
            "sixel_palette_parse_gpl: unsupported text encoding.");
1203
        return SIXEL_BAD_INPUT;
26✔
1204
    }
1205
    if (sixel_palette_contains_nul_byte(data, size)) {
372✔
1206
        sixel_helper_set_additional_message(
26✔
1207
            "sixel_palette_parse_gpl: embedded NUL byte.");
1208
        return SIXEL_BAD_INPUT;
26✔
1209
    }
1210

1211
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
346✔
1212
    if (text == NULL) {
346!
UNCOV
1213
        sixel_helper_set_additional_message(
×
1214
            "sixel_palette_parse_gpl: allocation failed.");
UNCOV
1215
        return SIXEL_BAD_ALLOCATION;
×
1216
    }
1217
    memcpy(text, data, size);
346✔
1218
    text[size] = '\0';
346✔
1219

1220
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
346✔
1221
        offset = 3u;
16✔
1222
    }
1✔
1223
    cursor = text + offset;
346✔
1224

1225
    while (*cursor != '\0') {
15,962✔
1226
        line = cursor;
234,990,512✔
1227
        line_end = cursor;
8,712✔
1228
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
537,098,670!
1229
            ++line_end;
537,082,872✔
1230
        }
1231
        if (*line_end != '\0') {
15,798✔
1232
            *line_end = '\0';
15,766✔
1233
            cursor = line_end + 1;
15,766✔
1234
        } else {
1,090✔
1235
            cursor = line_end;
4,386✔
1236
        }
1237
        while (*cursor == '\n' || *cursor == '\r') {
18,138!
1238
            ++cursor;
2,340✔
1239
        }
1240

1241
        while (*line == ' ' || *line == '\t') {
536,888,182!
1242
            ++line;
536,872,384✔
1243
        }
1244
        index = strlen(line);
15,798✔
1245
        while (index > 0u) {
15,798✔
1246
            tail = line[index - 1];
15,766✔
1247
            if (tail != ' ' && tail != '\t') {
15,766!
1248
                break;
8,694✔
1249
            }
UNCOV
1250
            line[index - 1] = '\0';
×
UNCOV
1251
            --index;
×
1252
        }
1253
        if (*line == '\0') {
15,798✔
1254
            continue;
32✔
1255
        }
1256
        if (*line == '#') {
15,766✔
1257
            continue;
84✔
1258
        }
1259
        if (strncmp(line, "Name:", 5) == 0) {
15,682✔
1260
            continue;
126✔
1261
        }
1262
        if (strncmp(line, "Columns:", 8) == 0) {
15,556✔
1263
            continue;
126✔
1264
        }
1265

1266
        if (!header_seen) {
15,430✔
1267
            if (strcmp(line, "GIMP Palette") != 0) {
320✔
1268
                sixel_helper_set_additional_message(
26✔
1269
                    "sixel_palette_parse_gpl: missing header.");
1270
                status = SIXEL_BAD_INPUT;
26✔
1271
                goto cleanup;
26✔
1272
            }
1273
            header_seen = 1;
294✔
1274
            continue;
294✔
1275
        }
1276

1277
        if (parsed_colors >= 256) {
15,110✔
1278
            sixel_helper_set_additional_message(
26✔
1279
                "sixel_palette_parse_gpl: too many colors.");
1280
            status = SIXEL_BAD_INPUT;
26✔
1281
            goto cleanup;
26✔
1282
        }
1283

1284
        value_index = 0;
8,321✔
1285
        while (value_index < 3) {
60,180✔
1286
            component = strtol(line, &parse_end, 10);
45,200✔
1287
            if (parse_end == line || component < 0L || component > 255L) {
45,200✔
1288
                sixel_helper_set_additional_message(
78✔
1289
                    "sixel_palette_parse_gpl: invalid component.");
1290
                status = SIXEL_BAD_INPUT;
78✔
1291
                goto cleanup;
78✔
1292
            }
1293
            /*
1294
             * Keep GPL compatibility with optional trailing labels,
1295
             * but reject numeric tokens with glued suffixes like "0x".
1296
             */
1297
            if (*parse_end != '\0' && *parse_end != ' '
45,122✔
1298
                    && *parse_end != '\t') {
10,299✔
1299
                sixel_helper_set_additional_message(
26✔
1300
                    "sixel_palette_parse_gpl: invalid component.");
1301
                status = SIXEL_BAD_INPUT;
26✔
1302
                goto cleanup;
26✔
1303
            }
1304
            values[value_index] = (int)component;
45,096✔
1305
            ++value_index;
45,096✔
1306
            line = parse_end;
45,096✔
1307
            while (*line == ' ' || *line == '\t') {
96,124✔
1308
                ++line;
51,028✔
1309
            }
1310
        }
1311
        /*
1312
         * Keep compatibility with trailing labels (for example "Index 0"),
1313
         * but reject a 4th numeric token after the RGB triplet.
1314
         */
1315
        if (*line != '\0') {
14,980✔
1316
            component = strtol(line, &parse_end, 10);
8,266✔
1317
            if (parse_end != line &&
8,266✔
1318
                    (*parse_end == '\0' || *parse_end == ' '
26!
1319
                     || *parse_end == '\t')) {
×
1320
                sixel_helper_set_additional_message(
26✔
1321
                    "sixel_palette_parse_gpl: invalid component.");
1322
                status = SIXEL_BAD_INPUT;
26✔
1323
                goto cleanup;
26✔
1324
            }
1325
        }
515✔
1326

1327
        palette_bytes[parsed_colors * 3 + 0] =
14,954✔
1328
            (unsigned char)values[0];
14,954✔
1329
        palette_bytes[parsed_colors * 3 + 1] =
14,954✔
1330
            (unsigned char)values[1];
14,954✔
1331
        palette_bytes[parsed_colors * 3 + 2] =
14,954✔
1332
            (unsigned char)values[2];
14,954✔
1333
        ++parsed_colors;
14,954✔
1334
    }
1335

1336
    if (!header_seen) {
164✔
1337
        sixel_helper_set_additional_message(
26✔
1338
            "sixel_palette_parse_gpl: header missing.");
1339
        status = SIXEL_BAD_INPUT;
26✔
1340
        goto cleanup;
26✔
1341
    }
1342
    if (parsed_colors <= 0) {
138✔
1343
        sixel_helper_set_additional_message(
26✔
1344
            "sixel_palette_parse_gpl: no colors parsed.");
1345
        status = SIXEL_BAD_INPUT;
26✔
1346
        goto cleanup;
26✔
1347
    }
1348

1349
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
112✔
1350
    if (SIXEL_FAILED(status)) {
112!
UNCOV
1351
        goto cleanup;
×
1352
    }
1353
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
112✔
1354
    local->gpu_policy = encoder->gpu_policy;
112✔
1355
    status = sixel_palette_import_dither_entries(
112✔
1356
        local,
7✔
1357
        palette_bytes,
7✔
1358
        (unsigned int)parsed_colors);
7✔
1359
    if (SIXEL_FAILED(status)) {
112!
UNCOV
1360
        goto cleanup;
×
1361
    }
1362

1363
    *dither = local;
112✔
1364
    status = SIXEL_OK;
112✔
1365

1366
cleanup:
164✔
1367
    if (SIXEL_FAILED(status) && local != NULL) {
382!
UNCOV
1368
        sixel_dither_unref(local);
×
1369
    }
1370
    if (text != NULL) {
346!
1371
        sixel_allocator_free(encoder->allocator, text);
346✔
1372
    }
25✔
1373
    return status;
246✔
1374
}
29✔
1375

1376

1377
/*
1378
 * Palette exporters
1379
 *
1380
 *   +----------+-------------------------+
1381
 *   | format   | emission strategy       |
1382
 *   +----------+-------------------------+
1383
 *   | ACT      | fixed 256 entries + EOF |
1384
 *   | PAL JASC | textual lines           |
1385
 *   | PAL RIFF | RIFF container          |
1386
 *   | GPL      | textual lines           |
1387
 *   +----------+-------------------------+
1388
 */
1389
SIXELSTATUS
1390
sixel_palette_write_act(FILE *stream,
32✔
1391
                        unsigned char const *palette,
1392
                        size_t palette_bytes,
1393
                        int exported_colors)
1394
{
1395
    SIXELSTATUS status;
14✔
1396
    unsigned char zero_pad[256 * 3];
14✔
1397
    unsigned char trailer[4];
14✔
1398
    size_t exported_bytes;
14✔
1399
    size_t pad_bytes;
14✔
1400

1401
    status = SIXEL_FALSE;
32✔
1402
    exported_bytes = 0u;
32✔
1403

1404
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
32!
1405
        return SIXEL_BAD_ARGUMENT;
1406
    }
1407
    if (exported_colors > 256) {
32!
1408
        exported_colors = 256;
1409
    }
1410

1411
    memset(zero_pad, 0, sizeof(zero_pad));
32!
1412
    exported_bytes = (size_t)exported_colors * 3u;
32✔
1413
    if (palette_bytes < exported_bytes) {
32!
1414
        return SIXEL_BAD_ARGUMENT;
1415
    }
1416
    pad_bytes = sizeof(zero_pad) - exported_bytes;
32✔
1417

1418
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
34✔
1419
                                 & 0xffu);
2✔
1420
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
32✔
1421
    trailer[2] = 0u;
32✔
1422
    trailer[3] = 0u;
32✔
1423

1424
    if (fwrite(palette, 1, exported_bytes, stream) != exported_bytes) {
32!
1425
        status = SIXEL_LIBC_ERROR;
8✔
1426
        return status;
1427
    }
1428
    if (pad_bytes > 0u &&
32!
UNCOV
1429
            fwrite(zero_pad, 1, pad_bytes, stream) != pad_bytes) {
×
1430
        status = SIXEL_LIBC_ERROR;
8✔
1431
        return status;
1432
    }
1433
    if (fwrite(trailer, 1, sizeof(trailer), stream)
32!
1434
            != sizeof(trailer)) {
2✔
UNCOV
1435
        status = SIXEL_LIBC_ERROR;
×
1436
        return status;
1437
    }
1438

1439
    return SIXEL_OK;
18✔
1440
}
2✔
1441

1442

1443
SIXELSTATUS
1444
sixel_palette_write_pal_jasc(FILE *stream,
784✔
1445
                             unsigned char const *palette,
1446
                             int exported_colors)
1447
{
1448
    int index;
343✔
1449

1450
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
784!
1451
        return SIXEL_BAD_ARGUMENT;
1452
    }
1453
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
784!
1454
        return SIXEL_LIBC_ERROR;
1455
    }
1456
    for (index = 0; index < exported_colors; ++index) {
22,256✔
1457
        if (fprintf(stream, "%d %d %d\n",
22,814!
1458
                    (int)palette[index * 3 + 0],
21,472✔
1459
                    (int)palette[index * 3 + 1],
21,472✔
1460
                    (int)palette[index * 3 + 2]) < 0) {
22,814✔
1461
            return SIXEL_LIBC_ERROR;
1462
        }
1463
    }
1,342✔
1464
    return SIXEL_OK;
441✔
1465
}
49✔
1466

1467

1468
SIXELSTATUS
1469
sixel_palette_write_pal_riff(FILE *stream,
32✔
1470
                             unsigned char const *palette,
1471
                             int exported_colors)
1472
{
1473
    unsigned char size_le[4];
14✔
1474
    unsigned char data_size_le[4];
14✔
1475
    unsigned char log_palette[4 + 256 * 4];
14✔
1476
    unsigned int data_size;
14✔
1477
    unsigned int riff_size;
14✔
1478
    int index;
14✔
1479

1480
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
32!
1481
        return SIXEL_BAD_ARGUMENT;
1482
    }
1483
    if (exported_colors > 256) {
32!
1484
        exported_colors = 256;
1485
    }
1486

1487
    data_size = 4u + (unsigned int)exported_colors * 4u;
32✔
1488
    riff_size = 4u + 8u + data_size;
32✔
1489

1490
    size_le[0] = (unsigned char)(riff_size & 0xffu);
32✔
1491
    size_le[1] = (unsigned char)((riff_size >> 8) & 0xffu);
32✔
1492
    size_le[2] = (unsigned char)((riff_size >> 16) & 0xffu);
32✔
1493
    size_le[3] = (unsigned char)((riff_size >> 24) & 0xffu);
32✔
1494
    data_size_le[0] = (unsigned char)(data_size & 0xffu);
32✔
1495
    data_size_le[1] = (unsigned char)((data_size >> 8) & 0xffu);
32✔
1496
    data_size_le[2] = (unsigned char)((data_size >> 16) & 0xffu);
32✔
1497
    data_size_le[3] = (unsigned char)((data_size >> 24) & 0xffu);
32✔
1498

1499
    memset(log_palette, 0, sizeof(log_palette));
32✔
1500
    log_palette[0] = 0x00;
32✔
1501
    log_palette[1] = 0x03;
32✔
1502
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
32✔
1503
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
32✔
1504
    for (index = 0; index < exported_colors; ++index) {
8,224✔
1505
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
8,192✔
1506
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
8,192✔
1507
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
8,192✔
1508
        log_palette[4 + index * 4 + 3] = 0u;
8,192✔
1509
    }
512✔
1510

1511
    if (fwrite("RIFF", 1, 4u, stream) != 4u) {
32!
1512
        return SIXEL_LIBC_ERROR;
1513
    }
1514
    if (fwrite(size_le, 1, sizeof(size_le), stream) != sizeof(size_le)) {
32!
1515
        return SIXEL_LIBC_ERROR;
1516
    }
1517
    if (fwrite("PAL ", 1, 4u, stream) != 4u) {
32!
1518
        return SIXEL_LIBC_ERROR;
1519
    }
1520
    if (fwrite("data", 1, 4u, stream) != 4u) {
32!
1521
        return SIXEL_LIBC_ERROR;
1522
    }
1523
    if (fwrite(data_size_le, 1, sizeof(data_size_le), stream)
32!
1524
            != sizeof(data_size_le)) {
2✔
1525
        return SIXEL_LIBC_ERROR;
1526
    }
1527
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
32!
1528
            != (size_t)data_size) {
18!
1529
        return SIXEL_LIBC_ERROR;
1530
    }
1531
    return SIXEL_OK;
18✔
1532
}
2✔
1533

1534

1535
SIXELSTATUS
1536
sixel_palette_write_gpl(FILE *stream,
80✔
1537
                        unsigned char const *palette,
1538
                        int exported_colors)
1539
{
1540
    int index;
35✔
1541

1542
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
80!
1543
        return SIXEL_BAD_ARGUMENT;
1544
    }
1545
    if (fprintf(stream, "GIMP Palette\n") < 0) {
80!
1546
        return SIXEL_LIBC_ERROR;
1547
    }
1548
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
80!
1549
        return SIXEL_LIBC_ERROR;
1550
    }
1551
    if (fprintf(stream, "Columns: 16\n") < 0) {
80!
1552
        return SIXEL_LIBC_ERROR;
1553
    }
1554
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
80!
1555
        return SIXEL_LIBC_ERROR;
1556
    }
1557
    for (index = 0; index < exported_colors; ++index) {
12,656✔
1558
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
13,362!
1559
                    (int)palette[index * 3 + 0],
12,576✔
1560
                    (int)palette[index * 3 + 1],
12,576✔
1561
                    (int)palette[index * 3 + 2],
12,576✔
1562
                    index) < 0) {
1,572✔
1563
            return SIXEL_LIBC_ERROR;
1564
        }
1565
    }
786✔
1566
    return SIXEL_OK;
45✔
1567
}
5✔
1568

1569
/* emacs Local Variables:      */
1570
/* emacs mode: c               */
1571
/* emacs tab-width: 4          */
1572
/* emacs indent-tabs-mode: nil */
1573
/* emacs c-basic-offset: 4     */
1574
/* emacs End:                  */
1575
/* vim: set expandtab ts=4 : */
1576
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc