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

saitoha / libsixel / 20578371047

29 Dec 2025 05:06PM UTC coverage: 51.955% (-5.4%) from 57.322%
20578371047

push

github

saitoha
Revert "Merge branch 'refactor/pixelformat' into develop"

This reverts commit 4a6153922, reversing
changes made to 6f3ef3068.

14746 of 45077 branches covered (32.71%)

147 of 262 new or added lines in 15 files covered. (56.11%)

1406 existing lines in 46 files now uncovered.

21419 of 41226 relevant lines covered (51.96%)

3895522.67 hits per line

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

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

26
#if defined(HAVE_CONFIG_H)
27
#include "config.h"
28
#endif
29
#if !defined(_POSIX_C_SOURCE)
30
# define _POSIX_C_SOURCE 200809L
31
#endif
32

33
/* STDC_HEADERS */
34
#include <stdio.h>
35
#include <stdlib.h>
36

37
#if HAVE_STRING_H
38
# include <string.h>
39
#endif
40
#if HAVE_CTYPE_H
41
# include <ctype.h>
42
#endif
43
#if HAVE_STDARG_H
44
# include <stdarg.h>
45
#endif
46
#if HAVE_SYS_TYPES_H
47
# include <sys/types.h>
48
#endif
49
#if HAVE_UNISTD_H
50
# include <unistd.h>
51
#endif
52
#if !defined(PATH_MAX)
53
#define PATH_MAX 4096
54
#endif
55
#if HAVE_FCNTL_H
56
# include <fcntl.h>
57
#endif
58
#if HAVE_SYS_TIME_H
59
# include <sys/time.h>
60
#elif HAVE_TIME_H
61
# include <time.h>
62
#endif  /* HAVE_SYS_TIME_H HAVE_TIME_H */
63
#if defined(_WIN32)
64
# if !defined(UNICODE)
65
#  define UNICODE
66
# endif
67
# if !defined(_UNICODE)
68
#  define _UNICODE
69
# endif
70
# if !defined(WIN32_LEAN_AND_MEAN)
71
#  define WIN32_LEAN_AND_MEAN
72
# endif
73
# include <windows.h>
74
#endif
75
#if HAVE_ERRNO_H
76
# include <errno.h>
77
#endif
78
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
79
# include <CoreServices/CoreServices.h>
80
#endif
81
#if HAVE_SYS_WAIT_H
82
# include <sys/wait.h>
83
#endif
84
#if HAVE_SPAWN_H
85
# include <spawn.h>
86
#endif
87
#if HAVE_LIMITS_H
88
# include <limits.h>
89
#endif
90
#if HAVE_DIRENT_H
91
# include <dirent.h>
92
#endif
93

94
#if !defined(HAVE_MEMCPY)
95
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
96
#endif
97

98
#include <sixel.h>
99
#include "loader.h"
100
#include "loader-builtin.h"
101
#include "loader-common.h"
102
#include "loader-coregraphics.h"
103
#include "loader-gd.h"
104
#include "loader-gdk-pixbuf2.h"
105
#include "loader-gnome-thumbnailer.h"
106
#include "loader-libjpeg.h"
107
#include "loader-libpng.h"
108
#include "loader-quicklook.h"
109
#include "loader-registry.h"
110
#include "loader-wic.h"
111
#include "compat_stub.h"
112
#include "frame.h"
113
#include "chunk.h"
114
#include "allocator.h"
115
#include "assessment.h"
116
#include "encoder.h"
117
#include "logger.h"
118

119
/*
120
 * Internal loader state carried across backends.  The fields mirror the
121
 * original `loader.c` layout to keep statistics, logging, and allocator
122
 * ownership centralized while implementations move into per-backend files.
123
 */
124
struct sixel_loader {
125
    int ref;
126
    int fstatic;
127
    int fuse_palette;
128
    int reqcolors;
129
    unsigned char bgcolor[3];
130
    int has_bgcolor;
131
    int loop_control;
132
    int finsecure;
133
    int const *cancel_flag;
134
    void *context;
135
    sixel_logger_t logger;
136
    sixel_assessment_t *assessment;
137
    char *loader_order;
138
    sixel_allocator_t *allocator;
139
    char last_loader_name[64];
140
    char last_source_path[PATH_MAX];
141
    size_t last_input_bytes;
142
    int callback_failed;
143
    int log_loader_finished;
144
    char log_path[PATH_MAX];
145
    char log_loader_name[64];
146
    size_t log_input_bytes;
147
};
148

149
typedef struct sixel_loader_callback_state {
150
    sixel_loader_t *loader;
151
    sixel_load_image_function fn;
152
    void *context;
153
} sixel_loader_callback_state_t;
154

155

156
#if HAVE_POSIX_SPAWNP
157
extern char **environ;
158
#endif
159

160
static char *
161
loader_strdup(char const *text, sixel_allocator_t *allocator)
×
162
{
163
    char *copy;
×
164
    size_t length;
×
165

166
    if (text == NULL) {
×
167
        return NULL;
168
    }
169

170
    length = strlen(text) + 1;
×
171
    copy = (char *)sixel_allocator_malloc(allocator, length);
×
172
    if (copy == NULL) {
×
173
        return NULL;
174
    }
175

176
#if HAVE_STRCPY_S
177
    (void)strcpy_s(copy, (rsize_t)(length - 1), text);
178
#else
179
    memcpy(copy, text, length);
×
180
#endif
181

182
    return copy;
×
183
}
184

185

186

187
/*
188
 * Emit loader stage markers.
189
 *
190
 * Loader callbacks run the downstream pipeline synchronously, so the finish
191
 * marker must be issued before invoking fn_load() to avoid inflating the
192
 * loader span. The helper keeps the formatting consistent with
193
 * sixel_encoder_log_stage() without depending on encoder internals.
194
 */
195
static void
196
loader_log_stage(sixel_loader_t *loader,
1,590✔
197
                 char const *event,
198
                 char const *fmt,
199
                 ...)
200
{
201
    sixel_logger_t *logger;
1,590✔
202
    char message[256];
1,590✔
203
    va_list args;
1,590✔
204

205
    logger = NULL;
1,590✔
206
    if (loader != NULL) {
1,590!
207
        logger = &loader->logger;
1,590✔
208
    }
209
    if (logger == NULL || logger->file == NULL || !logger->active) {
1,590!
210
        return;
1,590✔
211
    }
212

213
    message[0] = '\0';
×
214
#if defined(__clang__)
215
#pragma clang diagnostic push
216
#pragma clang diagnostic ignored "-Wformat-nonliteral"
217
#elif defined(__GNUC__)
218
#pragma GCC diagnostic push
219
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
220
#endif
221
    va_start(args, fmt);
×
222
    if (fmt != NULL) {
×
223
#if defined(__clang__)
224
#pragma clang diagnostic push
225
#pragma clang diagnostic ignored "-Wformat-nonliteral"
226
#elif defined(__GNUC__)
227
#pragma GCC diagnostic push
228
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
229
#endif
230
        (void)sixel_compat_vsnprintf(message, sizeof(message), fmt, args);
×
231
#if defined(__clang__)
232
#pragma clang diagnostic pop
233
#elif defined(__GNUC__)
234
#pragma GCC diagnostic pop
235
#endif
236
    }
237
    va_end(args);
×
238
#if defined(__clang__)
239
#pragma clang diagnostic pop
240
#elif defined(__GNUC__)
241
#pragma GCC diagnostic pop
242
#endif
243

244
    sixel_logger_logf(logger,
×
245
                      "worker",
246
                      "loader",
247
                      event,
248
                      -1,
249
                      -1,
250
                      0,
251
                      0,
252
                      0,
253
                      0,
254
                      "%s",
255
                      message);
256
}
1!
257

258
static SIXELSTATUS
259
loader_callback_trampoline(sixel_frame_t *frame, void *data)
916✔
260
{
261
    sixel_loader_callback_state_t *state;
916✔
262
    SIXELSTATUS status;
916✔
263
    sixel_loader_t *loader;
916✔
264

265
    state = (sixel_loader_callback_state_t *)data;
916✔
266
    loader = NULL;
916✔
267
    if (state == NULL || state->fn == NULL) {
916!
268
        return SIXEL_BAD_ARGUMENT;
269
    }
270

271
    loader = state->loader;
916✔
272
    if (loader != NULL && loader->log_loader_finished == 0) {
916!
273
        loader_log_stage(loader,
784✔
274
                         "finish",
275
                         "path=%s loader=%s bytes=%zu",
276
                         loader->log_path,
784✔
277
                         loader->log_loader_name,
784✔
278
                         loader->log_input_bytes);
279
        loader->log_loader_finished = 1;
784✔
280
    }
281

282
    status = state->fn(frame, state->context);
916✔
283
    if (SIXEL_FAILED(status) && state->loader != NULL) {
916!
284
        state->loader->callback_failed = 1;
8✔
285
    }
286

287
    return status;
288
}
289

290
static int
291
loader_plan_contains(sixel_loader_entry_t const **plan,
292
                     size_t plan_length,
293
                     sixel_loader_entry_t const *entry)
294
{
295
    size_t index;
296

297
    for (index = 0; index < plan_length; ++index) {
806!
UNCOV
298
        if (plan[index] == entry) {
×
299
            return 1;
300
        }
301
    }
302

303
    return 0;
304
}
305

306
static int
307
loader_token_matches(char const *token,
×
308
                     size_t token_length,
309
                     char const *name)
310
{
311
    size_t index;
×
312
    unsigned char left;
×
313
    unsigned char right;
×
314

315
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
×
316
        left = (unsigned char)token[index];
×
317
        right = (unsigned char)name[index];
×
318
        if (tolower(left) != tolower(right)) {
×
319
            return 0;
320
        }
321
    }
322

323
    if (index != token_length || name[index] != '\0') {
×
324
        return 0;
×
325
    }
326

327
    return 1;
328
}
329

330
static sixel_loader_entry_t const *
331
loader_lookup_token(char const *token,
×
332
                    size_t token_length,
333
                    sixel_loader_entry_t const *entries,
334
                    size_t entry_count)
335
{
336
    size_t index;
×
337

338
    for (index = 0; index < entry_count; ++index) {
×
339
        if (loader_token_matches(token,
×
340
                                 token_length,
341
                                 entries[index].name)) {
×
342
            return &entries[index];
343
        }
344
    }
345

346
    return NULL;
347
}
348

349
/*
350
 * loader_build_plan
351
 *
352
 * Translate a comma separated list into an execution plan that reorders the
353
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
354
 * are ignored so that new builds remain forward compatible.
355
 *
356
 *    user input "gd,coregraphics"
357
 *                |
358
 *                v
359
 *        +-------------------+
360
 *        | prioritized list  |
361
 *        +-------------------+
362
 *                |
363
 *                v
364
 *        +-------------------+
365
 *        | default fallbacks |
366
 *        +-------------------+
367
 */
368
static size_t
369
loader_build_plan(char const *order,
806✔
370
                  sixel_loader_entry_t const *entries,
371
                  size_t entry_count,
372
                  sixel_loader_entry_t const **plan,
373
                  size_t plan_capacity)
374
{
375
    size_t plan_length;
806✔
376
    size_t index;
806✔
377
    char const *cursor;
806✔
378
    char const *token_start;
806✔
379
    char const *token_end;
806✔
380
    size_t token_length;
806✔
381
    sixel_loader_entry_t const *entry;
806✔
382
    size_t limit;
806✔
383

384
    plan_length = 0;
806✔
385
    index = 0;
806✔
386
    cursor = order;
806✔
387
    token_start = order;
806✔
388
    token_end = order;
806✔
389
    token_length = 0;
806✔
390
    entry = NULL;
806✔
391
    limit = plan_capacity;
806✔
392

393
    if (order != NULL && plan != NULL && plan_capacity > 0) {
806!
394
        token_start = order;
395
        cursor = order;
396
        while (*cursor != '\0') {
×
397
            if (*cursor == ',') {
×
398
                token_end = cursor;
×
399
                while (token_start < token_end &&
×
400
                       isspace((unsigned char)*token_start)) {
×
401
                    ++token_start;
×
402
                }
403
                while (token_end > token_start &&
×
404
                       isspace((unsigned char)token_end[-1])) {
×
405
                    --token_end;
×
406
                }
407
                token_length = (size_t)(token_end - token_start);
×
408
                if (token_length > 0) {
×
409
                    entry = loader_lookup_token(token_start,
×
410
                                                token_length,
411
                                                entries,
412
                                                entry_count);
413
                    if (entry != NULL &&
×
414
                        !loader_plan_contains(plan,
×
415
                                              plan_length,
416
                                              entry) &&
×
417
                        plan_length < limit) {
418
                        plan[plan_length] = entry;
×
419
                        ++plan_length;
×
420
                    }
421
                }
422
                token_start = cursor + 1;
×
423
            }
424
            ++cursor;
×
425
        }
426

427
        token_end = cursor;
×
428
        while (token_start < token_end &&
×
429
               isspace((unsigned char)*token_start)) {
×
430
            ++token_start;
×
431
        }
432
        while (token_end > token_start &&
×
433
               isspace((unsigned char)token_end[-1])) {
×
434
            --token_end;
×
435
        }
436
        token_length = (size_t)(token_end - token_start);
×
437
        if (token_length > 0) {
×
438
            entry = loader_lookup_token(token_start,
×
439
                                        token_length,
440
                                        entries,
441
                                        entry_count);
442
            if (entry != NULL &&
×
443
                !loader_plan_contains(plan, plan_length, entry) &&
×
444
                plan_length < limit) {
445
                plan[plan_length] = entry;
×
446
                ++plan_length;
×
447
            }
448
        }
449
    }
450

451
    for (index = 0; index < entry_count && plan_length < limit; ++index) {
2,418✔
452
        entry = &entries[index];
1,612✔
453
        if (!entry->default_enabled) {
1,612✔
454
            continue;
806✔
455
        }
456
        if (!loader_plan_contains(plan, plan_length, entry)) {
806!
457
            plan[plan_length] = entry;
806✔
458
            ++plan_length;
806✔
459
        }
460
    }
461

462
    return plan_length;
806✔
463
}
464

465
static void
466
loader_append_chunk(char *dest,
168✔
467
                    size_t capacity,
468
                    size_t *offset,
469
                    char const *chunk)
470
{
471
    size_t available;
168✔
472
    size_t length;
168✔
473

474
    if (dest == NULL || offset == NULL || chunk == NULL) {
168!
475
        return;
476
    }
477

478
    if (*offset >= capacity) {
168!
479
        return;
480
    }
481

482
    available = capacity - *offset;
168✔
483
    if (available == 0) {
168!
484
        return;
485
    }
486

487
    length = strlen(chunk);
168✔
488
    if (length >= available) {
168!
489
        if (available == 0) {
×
490
            return;
491
        }
492
        length = available - 1u;
×
493
    }
494

495
    if (length > 0) {
168!
496
        memcpy(dest + *offset, chunk, length);
168✔
497
        *offset += length;
168✔
498
    }
499

500
    if (*offset < capacity) {
168!
501
        dest[*offset] = '\0';
168✔
502
    } else {
503
        dest[capacity - 1u] = '\0';
×
504
    }
505
}
1!
506

507
static void
508
loader_append_key_value(char *dest,
80✔
509
                        size_t capacity,
510
                        size_t *offset,
511
                        char const *label,
512
                        char const *value)
513
{
514
    char line[128];
80✔
515
    int written;
80✔
516

517
    if (value == NULL || value[0] == '\0') {
80!
518
        return;
×
519
    }
520

521
    written = sixel_compat_snprintf(line,
80✔
522
                                    sizeof(line),
523
                                    "  %-10s: %s\n",
524
                                    label,
525
                                    value);
526
    if (written < 0) {
80!
527
        return;
528
    }
529

530
    if ((size_t)written >= sizeof(line)) {
80!
UNCOV
531
        line[sizeof(line) - 1u] = '\0';
×
532
    }
533

534
    loader_append_chunk(dest, capacity, offset, line);
80✔
535
}
1!
536

537
static void
538
loader_extract_extension(char const *path, char *buffer, size_t capacity)
22✔
539
{
540
    char const *dot;
22✔
541
    size_t index;
22✔
542

543
    if (buffer == NULL || capacity == 0) {
22!
544
        return;
545
    }
546

547
    buffer[0] = '\0';
22✔
548

549
    if (path == NULL) {
22✔
550
        return;
551
    }
552

553
    dot = strrchr(path, '.');
18✔
554
    if (dot == NULL || dot[1] == '\0') {
18!
555
        return;
556
    }
557

558
#if defined(_WIN32)
559
    {
560
        char const *slash;
561
        char const *backslash;
562

563
        slash = strrchr(path, '/');
564
        backslash = strrchr(path, '\\');
565
        if ((slash != NULL && dot < slash) ||
566
                (backslash != NULL && dot < backslash)) {
567
            return;
568
        }
569
    }
570
#else
571
    {
572
        char const *slash;
18✔
573

574
        slash = strrchr(path, '/');
18✔
575
        if (slash != NULL && dot < slash) {
18!
576
            return;
577
        }
578
    }
1!
579
#endif
580

581
    if (dot[1] == '\0') {
1!
582
        return;
583
    }
584

585
    dot += 1;
72✔
586

587
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
72!
588
        buffer[index] = (char)tolower((unsigned char)dot[index]);
54✔
589
    }
590
    buffer[index] = '\0';
18✔
591
}
1!
592

593

594

595

596

597

598

599

600

601

602

603
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
604
static void
605
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
606
{
607
    if (buffer == NULL || capacity == 0) {
2!
608
        return;
609
    }
610

611
    buffer[0] = '\0';
612
    if (source == NULL) {
1!
613
        return;
614
    }
615

616
    if (!CFStringGetCString(source,
2!
617
                             buffer,
618
                             (CFIndex)capacity,
619
                             kCFStringEncodingUTF8)) {
620
        buffer[0] = '\0';
621
    }
622
}
623
#endif
624

625

626
static void
627
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
22✔
628
                          char const *filename)
629
{
630
    enum { description_length = 128 };
22✔
631
    enum { uttype_length = 128 };
22✔
632
    enum { extension_length = 32 };
22✔
633
    enum { message_length = 768 };
22✔
634
    char message[message_length];
22✔
635
    char type_value[description_length];
22✔
636
    char extension_text[extension_length + 2];
22✔
637
    char uttype[uttype_length];
22✔
638
    char desc_buffer[description_length];
22✔
639
    char extension[extension_length];
22✔
640
    char const *path;
22✔
641
    char const *display_path;
22✔
642
    char const *metadata_path;
22✔
643
    char const *description_text;
22✔
644
    char *mime_string;
22✔
645
    char *description_string;
22✔
646
    size_t offset;
22✔
647
    int gnome_available;
22✔
648
    int gnome_has_dirs;
22✔
649
    int gnome_has_match;
22✔
650
    int suggestions;
22✔
651

652
    message[0] = '\0';
22✔
653
    type_value[0] = '\0';
22✔
654
    extension_text[0] = '\0';
22✔
655
    uttype[0] = '\0';
22✔
656
    desc_buffer[0] = '\0';
22✔
657
    extension[0] = '\0';
22✔
658
    path = NULL;
22✔
659
    display_path = "(stdin)";
22✔
660
    metadata_path = NULL;
22✔
661
    description_text = NULL;
22✔
662
    mime_string = NULL;
22✔
663
    description_string = NULL;
22✔
664
    offset = 0u;
22✔
665
    gnome_available = 0;
22✔
666
    gnome_has_dirs = 0;
22✔
667
    gnome_has_match = 0;
22✔
668
    suggestions = 0;
22✔
669

670
    if (pchunk != NULL && pchunk->source_path != NULL) {
22!
671
        path = pchunk->source_path;
672
    } else if (filename != NULL) {
4!
673
        path = filename;
674
    }
675

676
    if (path != NULL && strcmp(path, "-") != 0) {
18!
677
        display_path = path;
18✔
678
    }
679

680
    if (path != NULL && strcmp(path, "-") != 0 &&
18!
681
            strstr(path, "://") == NULL) {
18!
682
        metadata_path = path;
22✔
683
    }
684

685
    loader_extract_extension(path, extension, sizeof(extension));
22✔
686

687
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
688
    if (metadata_path != NULL) {
22✔
689
        /*
690
         * Collect MIME metadata via file(1) when fork() and friends are
691
         * available.  Windows builds compiled with clang64 lack these
692
         * interfaces, so the thumbnail helpers remain disabled there.
693
         */
694
        mime_string = thumbnailer_guess_content_type(metadata_path);
18✔
695
        description_string = thumbnailer_run_file(metadata_path, NULL);
18✔
696
    }
697
#else
698
    (void)metadata_path;
699
#endif
700

701
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
702
#if defined(__clang__)
703
    /*
704
     * Allow use of legacy UTType C APIs when compiling with the
705
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
706
     * so we must intentionally silence the deprecation warnings here.
707
     */
708
#pragma clang diagnostic push
709
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
710
#endif
711
    {
712
        CFStringRef uti_ref;
713
        CFStringRef mime_ref;
714
        CFStringRef ext_ref;
715
        CFStringRef desc_ref;
716
        CFStringRef preferred_mime;
717
        char uti_local[uttype_length];
718
        char desc_local[description_length];
719
        char mime_local[64];
720

721
        uti_ref = NULL;
722
        mime_ref = NULL;
723
        ext_ref = NULL;
724
        desc_ref = NULL;
725
        preferred_mime = NULL;
726
        uti_local[0] = '\0';
727
        desc_local[0] = '\0';
728
        mime_local[0] = '\0';
729

730
        if (mime_string != NULL) {
2✔
731
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
732
                                                 mime_string,
733
                                                 kCFStringEncodingUTF8);
734
        }
735
        if (mime_ref != NULL) {
2✔
736
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
737
                kUTTagClassMIMEType,
738
                mime_ref,
739
                NULL);
740
        }
741
        if (uti_ref == NULL && extension[0] != '\0') {
3!
742
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
743
                                                extension,
744
                                                kCFStringEncodingUTF8);
745
            if (ext_ref != NULL) {
×
746
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
747
                    kUTTagClassFilenameExtension,
748
                    ext_ref,
749
                    NULL);
750
            }
751
        }
752
        if (uti_ref != NULL) {
2✔
753
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
754
            desc_ref = UTTypeCopyDescription(uti_ref);
755
            if (desc_ref != NULL) {
1!
756
                loader_copy_cfstring(desc_ref,
757
                                     desc_local,
758
                                     sizeof(desc_local));
759
                CFRelease(desc_ref);
760
                desc_ref = NULL;
761
            }
762
            if (mime_string == NULL) {
1!
763
                preferred_mime = UTTypeCopyPreferredTagWithClass(
764
                    uti_ref,
765
                    kUTTagClassMIMEType);
766
                if (preferred_mime != NULL) {
×
767
                    loader_copy_cfstring(preferred_mime,
768
                                         mime_local,
769
                                         sizeof(mime_local));
770
                    CFRelease(preferred_mime);
771
                    preferred_mime = NULL;
772
                }
773
                if (mime_local[0] != '\0') {
×
774
                    mime_string = thumbnailer_strdup(mime_local);
775
                }
776
            }
777
        }
778
        if (mime_ref != NULL) {
2✔
779
            CFRelease(mime_ref);
780
        }
781
        if (ext_ref != NULL) {
1!
782
            CFRelease(ext_ref);
783
        }
784
        if (uti_ref != NULL) {
2✔
785
            CFRelease(uti_ref);
786
        }
787
        if (uti_local[0] != '\0') {
2✔
788
            sixel_compat_snprintf(uttype,
789
                                  sizeof(uttype),
790
                                  "%s",
791
                                  uti_local);
792
        }
793
        if (desc_local[0] != '\0') {
2✔
794
            sixel_compat_snprintf(desc_buffer,
795
                                  sizeof(desc_buffer),
796
                                  "%s",
797
                                  desc_local);
798
        }
799
    }
800
#if defined(__clang__)
801
#pragma clang diagnostic pop
802
#endif
803
#endif
804

805
    if (description_string != NULL && description_string[0] != '\0') {
18!
806
        description_text = description_string;
807
    } else if (desc_buffer[0] != '\0') {
4!
808
        description_text = desc_buffer;
809
    } else {
810
        description_text = "unknown content";
4✔
811
    }
812

813
    sixel_compat_snprintf(type_value,
22✔
814
                          sizeof(type_value),
815
                          "%s",
816
                          description_text);
817

818
    loader_append_chunk(message,
22✔
819
                        sizeof(message),
820
                        &offset,
821
                        "diagnostic:\n");
822
    loader_append_key_value(message,
22✔
823
                            sizeof(message),
824
                            &offset,
825
                            "file",
826
                            display_path);
827
    loader_append_key_value(message,
22✔
828
                            sizeof(message),
829
                            &offset,
830
                            "type",
831
                            type_value);
832

833
    if (mime_string != NULL && mime_string[0] != '\0') {
22!
834
        loader_append_key_value(message,
18✔
835
                                sizeof(message),
836
                                &offset,
837
                                "mime",
838
                                mime_string);
839
    }
840

841
    if (uttype[0] != '\0') {
22!
UNCOV
842
        loader_append_key_value(message,
×
843
                                sizeof(message),
844
                                &offset,
845
                                "uti",
846
                                uttype);
847
    }
848

849
    if (extension[0] != '\0') {
22✔
850
        sixel_compat_snprintf(extension_text,
18✔
851
                              sizeof(extension_text),
852
                              ".%s",
853
                              extension);
854
        loader_append_key_value(message,
18✔
855
                                sizeof(message),
856
                                &offset,
857
                                "extension",
858
                                extension_text);
859
    }
860

861
    loader_append_chunk(message,
22✔
862
                        sizeof(message),
863
                        &offset,
864
                        "  suggestions:\n");
865

866
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
867
    int quicklook_available;
868
    int quicklook_supported;
869

870
    quicklook_available = 0;
871
    quicklook_supported = 0;
872

873
    quicklook_available = loader_registry_entry_available("quicklook");
874
    if (quicklook_available) {
1!
875
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
876
    }
877
    if (quicklook_supported) {
1!
878
        loader_append_chunk(message,
879
                            sizeof(message),
880
                            &offset,
881
                            "    - QuickLook rendered a preview during "
882
                            "the probe; try -j quicklook.\n");
883
        suggestions += 1;
884
    }
885
#endif
886

887
#if HAVE_UNISTD_H && HAVE_SYS_WAIT_H && HAVE_FORK
888
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
22✔
889
    if (gnome_available) {
22!
890
        loader_probe_gnome_thumbnailers(mime_string,
22✔
891
                                        &gnome_has_dirs,
892
                                        &gnome_has_match);
893
        if (gnome_has_dirs && gnome_has_match) {
22!
894
            loader_append_chunk(message,
×
895
                                sizeof(message),
896
                                &offset,
897
                                "    - GNOME thumbnailer definitions match "
898
                                "this MIME type; try -j gnome-thumbnailer.\n"
899
                                );
900
            suggestions += 1;
×
901
        }
902
    }
903
#else
904
    (void)gnome_available;
905
    (void)gnome_has_dirs;
906
    (void)gnome_has_match;
907
#endif
908

UNCOV
909
    if (suggestions == 0) {
×
910
        loader_append_chunk(message,
22✔
911
                            sizeof(message),
912
                            &offset,
913
                            "    (no thumbnail helper hints)\n");
914
    }
915

916
    if (suggestions > 0) {
22!
917
        loader_append_chunk(message,
×
918
                            sizeof(message),
919
                            &offset,
920
                            "  hint       : Enable one of the suggested "
921
                            "loaders with -j.\n");
922
    } else {
923
        loader_append_chunk(message,
22✔
924
                            sizeof(message),
925
                            &offset,
926
                            "  hint       : Convert the file to PNG or "
927
                            "enable optional loaders.\n");
928
    }
929

930
    sixel_helper_set_additional_message(message);
22✔
931

932
    free(mime_string);
22✔
933
    free(description_string);
22✔
934
}
22✔
935

936
SIXELAPI SIXELSTATUS
937
sixel_loader_new(
806✔
938
    sixel_loader_t   /* out */ **pploader,
939
    sixel_allocator_t/* in */  *allocator)
940
{
941
    SIXELSTATUS status = SIXEL_FALSE;
806✔
942
    sixel_loader_t *loader;
806✔
943
    sixel_allocator_t *local_allocator;
806✔
944

945
    loader = NULL;
806✔
946
    local_allocator = allocator;
806✔
947

948
    if (pploader == NULL) {
806!
949
        sixel_helper_set_additional_message(
×
950
            "sixel_loader_new: pploader is null.");
951
        status = SIXEL_BAD_ARGUMENT;
×
952
        goto end;
×
953
    }
954

955
    if (local_allocator == NULL) {
806!
956
        status = sixel_allocator_new(&local_allocator,
×
957
                                     NULL,
958
                                     NULL,
959
                                     NULL,
960
                                     NULL);
961
        if (SIXEL_FAILED(status)) {
×
962
            goto end;
×
963
        }
964
    } else {
965
        sixel_allocator_ref(local_allocator);
806✔
966
    }
967

968
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
806✔
969
                                                      sizeof(*loader));
970
    if (loader == NULL) {
806!
971
        sixel_helper_set_additional_message(
×
972
            "sixel_loader_new: sixel_allocator_malloc() failed.");
973
        status = SIXEL_BAD_ALLOCATION;
×
974
        sixel_allocator_unref(local_allocator);
×
975
        goto end;
×
976
    }
977

978
    loader->ref = 1;
806✔
979
    loader->fstatic = 0;
806✔
980
    loader->fuse_palette = 0;
806✔
981
    loader->reqcolors = SIXEL_PALETTE_MAX;
806✔
982
    loader->bgcolor[0] = 0;
806✔
983
    loader->bgcolor[1] = 0;
806✔
984
    loader->bgcolor[2] = 0;
806✔
985
    loader->has_bgcolor = 0;
806✔
986
    loader->loop_control = SIXEL_LOOP_AUTO;
806✔
987
    loader->finsecure = 0;
806✔
988
    loader->cancel_flag = NULL;
806✔
989
    loader->context = NULL;
806✔
990
    /*
991
     * Initialize a private logger. The helper reuses an existing global
992
     * logger sink when present so loader markers share the timeline with
993
     * upstream stages without requiring sixel_loader_setopt().
994
     */
995
    sixel_logger_init(&loader->logger);
806✔
996
    (void)sixel_logger_prepare_env(&loader->logger);
806✔
997
    loader->assessment = NULL;
806✔
998
    loader->loader_order = NULL;
806✔
999
    loader->allocator = local_allocator;
806✔
1000
    loader->last_loader_name[0] = '\0';
806✔
1001
    loader->last_source_path[0] = '\0';
806✔
1002
    loader->last_input_bytes = 0u;
806✔
1003
    loader->callback_failed = 0;
806✔
1004
    loader->log_loader_finished = 0;
806✔
1005
    loader->log_path[0] = '\0';
806✔
1006
    loader->log_loader_name[0] = '\0';
806✔
1007
    loader->log_input_bytes = 0u;
806✔
1008

1009
    *pploader = loader;
806✔
1010
    status = SIXEL_OK;
806✔
1011

1012
end:
806✔
1013
    return status;
806✔
1014
}
1015

1016
SIXELAPI void
1017
sixel_loader_ref(
8,238✔
1018
    sixel_loader_t /* in */ *loader)
1019
{
1020
    if (loader == NULL) {
6,341!
1021
        return;
1022
    }
1023

1024
    ++loader->ref;
6,341✔
1025
}
1026

1027
SIXELAPI void
1028
sixel_loader_unref(
9,044✔
1029
    sixel_loader_t /* in */ *loader)
1030
{
1031
    sixel_allocator_t *allocator;
9,044✔
1032

1033
    if (loader == NULL) {
9,044!
1034
        return;
1035
    }
1036

1037
    if (--loader->ref == 0) {
9,044✔
1038
        allocator = loader->allocator;
806✔
1039
        sixel_logger_close(&loader->logger);
806✔
1040
        sixel_allocator_free(allocator, loader->loader_order);
806✔
1041
        sixel_allocator_free(allocator, loader);
806✔
1042
        sixel_allocator_unref(allocator);
806✔
1043
    }
1044
}
1!
1045

1046
SIXELAPI SIXELSTATUS
1047
sixel_loader_setopt(
7,432✔
1048
    sixel_loader_t /* in */ *loader,
1049
    int            /* in */ option,
1050
    void const     /* in */ *value)
1051
{
1052
    SIXELSTATUS status = SIXEL_FALSE;
7,432✔
1053
    int const *flag;
7,432✔
1054
    unsigned char const *color;
7,432✔
1055
    char const *order;
7,432✔
1056
    char *copy;
7,432✔
1057
    sixel_allocator_t *allocator;
7,432✔
1058

1059
    flag = NULL;
7,432✔
1060
    color = NULL;
7,432✔
1061
    order = NULL;
7,432✔
1062
    copy = NULL;
7,432✔
1063
    allocator = NULL;
7,432✔
1064

1065
    if (loader == NULL) {
7,432!
1066
        sixel_helper_set_additional_message(
×
1067
            "sixel_loader_setopt: loader is null.");
1068
        status = SIXEL_BAD_ARGUMENT;
×
1069
        goto end0;
×
1070
    }
1071

1072
    sixel_loader_ref(loader);
7,432✔
1073

1074
    switch (option) {
7,432!
1075
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
806✔
1076
        flag = (int const *)value;
806✔
1077
        loader->fstatic = flag != NULL ? *flag : 0;
806!
1078
        status = SIXEL_OK;
806✔
1079
        break;
806✔
1080
    case SIXEL_LOADER_OPTION_USE_PALETTE:
806✔
1081
        flag = (int const *)value;
806✔
1082
        loader->fuse_palette = flag != NULL ? *flag : 0;
806!
1083
        status = SIXEL_OK;
806✔
1084
        break;
806✔
1085
    case SIXEL_LOADER_OPTION_REQCOLORS:
806✔
1086
        flag = (int const *)value;
806✔
1087
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
806!
1088
        if (loader->reqcolors < 1) {
806!
1089
            sixel_helper_set_additional_message(
×
1090
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1091
            status = SIXEL_BAD_ARGUMENT;
×
1092
            goto end;
×
1093
        }
1094
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
806!
1095
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1096
        }
1097
        status = SIXEL_OK;
1098
        break;
1099
    case SIXEL_LOADER_OPTION_BGCOLOR:
656✔
1100
        if (value == NULL) {
656✔
1101
            loader->has_bgcolor = 0;
628✔
1102
        } else {
1103
            color = (unsigned char const *)value;
28✔
1104
            loader->bgcolor[0] = color[0];
28✔
1105
            loader->bgcolor[1] = color[1];
28✔
1106
            loader->bgcolor[2] = color[2];
28✔
1107
            loader->has_bgcolor = 1;
28✔
1108
        }
1109
        status = SIXEL_OK;
1110
        break;
1111
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
806✔
1112
        flag = (int const *)value;
806✔
1113
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
806!
1114
        status = SIXEL_OK;
806✔
1115
        break;
806✔
1116
    case SIXEL_LOADER_OPTION_INSECURE:
806✔
1117
        flag = (int const *)value;
806✔
1118
        loader->finsecure = flag != NULL ? *flag : 0;
806!
1119
        status = SIXEL_OK;
806✔
1120
        break;
806✔
1121
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
656✔
1122
        loader->cancel_flag = (int const *)value;
656✔
1123
        status = SIXEL_OK;
656✔
1124
        break;
656✔
1125
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
656✔
1126
        allocator = loader->allocator;
656✔
1127
        sixel_allocator_free(allocator, loader->loader_order);
656✔
1128
        loader->loader_order = NULL;
656✔
1129
        if (value != NULL) {
656!
1130
            order = (char const *)value;
×
1131
            copy = loader_strdup(order, allocator);
×
1132
            if (copy == NULL) {
×
1133
                sixel_helper_set_additional_message(
×
1134
                    "sixel_loader_setopt: loader_strdup() failed.");
1135
                status = SIXEL_BAD_ALLOCATION;
×
1136
                goto end;
×
1137
            }
1138
            loader->loader_order = copy;
×
1139
        }
1140
        status = SIXEL_OK;
1141
        break;
1142
    case SIXEL_LOADER_OPTION_CONTEXT:
806✔
1143
        loader->context = (void *)value;
806✔
1144
        loader->assessment = NULL;
806✔
1145
        status = SIXEL_OK;
806✔
1146
        break;
806✔
1147
    case SIXEL_LOADER_OPTION_ASSESSMENT:
628✔
1148
        loader->assessment = (sixel_assessment_t *)value;
628✔
1149
        status = SIXEL_OK;
628✔
1150
        break;
628✔
UNCOV
1151
    default:
×
1152
        sixel_helper_set_additional_message(
×
1153
            "sixel_loader_setopt: unknown option.");
1154
        status = SIXEL_BAD_ARGUMENT;
×
1155
        goto end;
×
1156
    }
1157

1158
end:
7,432✔
1159
    sixel_loader_unref(loader);
7,432✔
1160

1161
end0:
7,432✔
1162
    return status;
7,432✔
1163
}
1164

1165
SIXELAPI char const *
1166
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
1,216✔
1167
{
1168
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
1,216!
1169
        return NULL;
1170
    }
1171
    return loader->last_loader_name;
1,216✔
1172
}
1173

1174
SIXELAPI char const *
1175
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
1,032✔
1176
{
1177
    if (loader == NULL || loader->last_source_path[0] == '\0') {
1,032!
1178
        return NULL;
1179
    }
1180
    return loader->last_source_path;
848✔
1181
}
1182

1183
SIXELAPI size_t
1184
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
608✔
1185
{
1186
    if (loader == NULL) {
608!
1187
        return 0u;
1188
    }
1189
    return loader->last_input_bytes;
608✔
1190
}
1191

1192
SIXELAPI SIXELSTATUS
1193
sixel_loader_load_file(
806✔
1194
    sixel_loader_t         /* in */ *loader,
1195
    char const             /* in */ *filename,
1196
    sixel_load_image_function /* in */ fn_load)
1197
{
1198
    SIXELSTATUS status = SIXEL_FALSE;
806✔
1199
    sixel_chunk_t *pchunk;
806✔
1200
    sixel_loader_entry_t const **plan;
806✔
1201
    sixel_loader_entry_t const *entries;
806✔
1202
    size_t entry_count;
806✔
1203
    size_t plan_length;
806✔
1204
    size_t plan_index;
806✔
1205
    unsigned char *bgcolor;
806✔
1206
    int reqcolors;
806✔
1207
    sixel_assessment_t *assessment;
806✔
1208
    char const *order_override;
806✔
1209
    char const *env_order;
806✔
1210
    sixel_loader_callback_state_t callback_state;
806✔
1211

1212
    pchunk = NULL;
806✔
1213
    plan = NULL;
806✔
1214
    entries = NULL;
806✔
1215
    entry_count = 0;
806✔
1216
    plan_length = 0;
806✔
1217
    plan_index = 0;
806✔
1218
    bgcolor = NULL;
806✔
1219
    reqcolors = 0;
806✔
1220
    assessment = NULL;
806✔
1221
    order_override = NULL;
806✔
1222
    env_order = NULL;
806✔
1223

1224
    if (loader == NULL) {
806!
1225
        sixel_helper_set_additional_message(
×
1226
            "sixel_loader_load_file: loader is null.");
1227
        status = SIXEL_BAD_ARGUMENT;
×
1228
        goto end0;
×
1229
    }
1230

1231
    sixel_loader_ref(loader);
806✔
1232

1233
    loader->log_loader_finished = 0;
806✔
1234
    loader->log_loader_name[0] = '\0';
806✔
1235
    loader->log_input_bytes = 0u;
806✔
1236
    loader->log_path[0] = '\0';
806✔
1237
    if (filename != NULL) {
806✔
1238
        (void)sixel_compat_snprintf(loader->log_path,
618✔
1239
                                    sizeof(loader->log_path),
1240
                                    "%s",
1241
                                    filename);
1242
    }
1243
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
806✔
1244

1245
    memset(&callback_state, 0, sizeof(callback_state));
806✔
1246
    callback_state.loader = loader;
806✔
1247
    callback_state.fn = fn_load;
806✔
1248
    callback_state.context = loader->context;
806✔
1249
    loader->callback_failed = 0;
806✔
1250

1251
    entry_count = loader_registry_get_entries(&entries);
806✔
1252

1253
    reqcolors = loader->reqcolors;
806✔
1254
    if (reqcolors > SIXEL_PALETTE_MAX) {
806!
1255
        reqcolors = SIXEL_PALETTE_MAX;
1256
    }
1257

1258
    assessment = loader->assessment;
806✔
1259

1260
    /*
1261
     *  Assessment pipeline sketch:
1262
     *
1263
     *      +-------------+        +--------------+
1264
     *      | chunk read  | -----> | image decode |
1265
     *      +-------------+        +--------------+
1266
     *
1267
     *  The loader owns the hand-off.  Chunk I/O ends before any decoder runs,
1268
     *  so we time the read span in the encoder and pivot to decode once the
1269
     *  chunk is populated.
1270
     */
1271
    status = sixel_chunk_new(&pchunk,
806✔
1272
                             filename,
1273
                             loader->finsecure,
1274
                             loader->cancel_flag,
1275
                             loader->allocator);
1276
    if (status != SIXEL_OK) {
806!
1277
        goto end;
×
1278
    }
1279

1280
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
806!
UNCOV
1281
        status = SIXEL_OK;
×
UNCOV
1282
        goto end;
×
1283
    }
1284

1285
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
806!
1286
        (void)sixel_compat_snprintf(loader->log_path,
618✔
1287
                                    sizeof(loader->log_path),
1288
                                    "%s",
1289
                                    pchunk->source_path);
1290
    }
1291

1292
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
806!
1293
        status = SIXEL_LOGIC_ERROR;
×
1294
        goto end;
×
1295
    }
1296

1297
    if (loader->has_bgcolor) {
806✔
1298
        bgcolor = loader->bgcolor;
28✔
1299
    }
1300

1301
    status = SIXEL_FALSE;
806✔
1302
    if (assessment != NULL) {
806✔
1303
        sixel_assessment_stage_transition(
4✔
1304
            assessment,
1305
            SIXEL_ASSESSMENT_STAGE_IMAGE_DECODE);
1306
    }
1307
    order_override = loader->loader_order;
806✔
1308
    /*
1309
     * Honour SIXEL_LOADER_PRIORITY_LIST when callers do not supply
1310
     * a loader order via -L/--loaders or sixel_loader_setopt().
1311
     */
1312
    if (order_override == NULL) {
806!
1313
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
806✔
1314
        if (env_order != NULL && env_order[0] != '\0') {
806!
1315
            order_override = env_order;
806✔
1316
        }
1317
    }
1318

1319
    plan = sixel_allocator_malloc(loader->allocator,
806✔
1320
                                  entry_count * sizeof(*plan));
1321
    if (plan == NULL) {
806!
1322
        status = SIXEL_BAD_ALLOCATION;
×
1323
        goto end;
×
1324
    }
1325

1326
    plan_length = loader_build_plan(order_override,
806✔
1327
                                    entries,
1328
                                    entry_count,
1329
                                    plan,
1330
                                    entry_count);
1331

1332
    for (plan_index = 0; plan_index < plan_length; ++plan_index) {
1,642✔
1333
        if (plan[plan_index] == NULL) {
806!
1334
            continue;
×
1335
        }
1336
        if (plan[plan_index]->predicate != NULL &&
806!
UNCOV
1337
            plan[plan_index]->predicate(pchunk) == 0) {
×
UNCOV
1338
            continue;
×
1339
        }
1340
        loader->log_input_bytes = pchunk != NULL ? pchunk->size : 0u;
806!
1341
        if (plan[plan_index]->name != NULL) {
806!
1342
            (void)sixel_compat_snprintf(loader->log_loader_name,
806✔
1343
                                        sizeof(loader->log_loader_name),
1344
                                        "%s",
1345
                                        plan[plan_index]->name);
1346
        } else {
1347
            loader->log_loader_name[0] = '\0';
×
1348
        }
1349
        loader_trace_try(plan[plan_index]->name);
806✔
1350
        status = plan[plan_index]->backend(pchunk,
806✔
1351
                                           loader->fstatic,
1352
                                           loader->fuse_palette,
1353
                                           reqcolors,
1354
                                           bgcolor,
1355
                                           loader->loop_control,
1356
                                           loader_callback_trampoline,
1357
                                           &callback_state);
1358
        loader_trace_result(plan[plan_index]->name, status);
806✔
1359
        if (SIXEL_SUCCEEDED(status)) {
806✔
1360
            break;
1361
        }
1362
    }
1363

1364
    if (SIXEL_FAILED(status)) {
806✔
1365
        if (!loader->callback_failed &&
30!
1366
                plan_length > 0u &&
22!
1367
                plan_index >= plan_length &&
22!
1368
                pchunk != NULL) {
22!
1369
            status = SIXEL_LOADER_FAILED;
22✔
1370
            loader_publish_diagnostic(pchunk, filename);
22✔
1371
        }
1372
        goto end;
30✔
1373
    }
1374

1375
    if (plan_index < plan_length &&
776!
1376
            plan[plan_index] != NULL &&
776!
1377
            plan[plan_index]->name != NULL) {
776!
1378
        (void)sixel_compat_snprintf(loader->last_loader_name,
776✔
1379
                                    sizeof(loader->last_loader_name),
1380
                                    "%s",
1381
                                    plan[plan_index]->name);
1382
    } else {
1383
        loader->last_loader_name[0] = '\0';
×
1384
    }
1385
    loader->last_input_bytes = pchunk->size;
776✔
1386
    if (pchunk->source_path != NULL) {
776✔
1387
        size_t path_len;
592✔
1388

1389
        path_len = strlen(pchunk->source_path);
592✔
1390
        if (path_len >= sizeof(loader->last_source_path)) {
592!
1391
            path_len = sizeof(loader->last_source_path) - 1u;
1392
        }
1393
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
592✔
1394
        loader->last_source_path[path_len] = '\0';
592✔
1395
    } else {
1396
        loader->last_source_path[0] = '\0';
184✔
1397
    }
1398

1399
end:
806✔
UNCOV
1400
    if (plan != NULL) {
✔
1401
        sixel_allocator_free(loader->allocator, plan);
806✔
1402
        plan = NULL;
806✔
1403
    }
1404
    sixel_chunk_destroy(pchunk);
806✔
1405
    sixel_loader_unref(loader);
806✔
1406

1407
end0:
806✔
1408
    return status;
806✔
1409
}
1410

1411
/* load image from file */
1412

1413
SIXELAPI SIXELSTATUS
1414
sixel_helper_load_image_file(
×
1415
    char const                /* in */     *filename,     /* source file name */
1416
    int                       /* in */     fstatic,       /* whether to */
1417
                                                             /* extract a */
1418
                                                             /* static image */
1419
                                                             /* from an */
1420
                                                             /* animated gif */
1421
    int                       /* in */     fuse_palette,  /* whether to */
1422
                                                             /* use a */
1423
                                                             /* paletted */
1424
                                                             /* image; set */
1425
                                                             /* non-zero to */
1426
                                                             /* request one */
1427
    int                       /* in */     reqcolors,     /* requested */
1428
                                                             /* number of */
1429
                                                             /* colors; */
1430
                                                             /* should be */
1431
                                                             /* equal to or */
1432
                                                             /* less than */
1433
                                                             /* SIXEL_ */
1434
                                                             /* PALETTE_ */
1435
                                                             /* MAX */
1436
    unsigned char             /* in */     *bgcolor,      /* background */
1437
                                                             /* color, may */
1438
                                                             /* be NULL */
1439
    int                       /* in */     loop_control,  /* one of enum */
1440
                                                             /* loopControl */
1441
    sixel_load_image_function /* in */     fn_load,       /* callback */
1442
    int                       /* in */     finsecure,     /* true if do */
1443
                                                             /* not verify */
1444
                                                             /* SSL */
1445
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
1446
                                                             /* may be */
1447
                                                             /* NULL */
1448
    void                      /* in/out */ *context,      /* private data */
1449
                                                             /* passed to */
1450
                                                             /* callback */
1451
                                                             /* function, */
1452
                                                             /* may be */
1453
                                                             /* NULL */
1454
    sixel_allocator_t         /* in */     *allocator     /* allocator */
1455
                                                             /* object, */
1456
                                                             /* may be */
1457
                                                             /* NULL */
1458
)
1459
{
1460
    SIXELSTATUS status = SIXEL_FALSE;
×
1461
    sixel_loader_t *loader;
×
1462

1463
    loader = NULL;
×
1464

1465
    status = sixel_loader_new(&loader, allocator);
×
1466
    if (SIXEL_FAILED(status)) {
×
1467
        goto end;
×
1468
    }
1469

1470
    status = sixel_loader_setopt(loader,
×
1471
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1472
                                 &fstatic);
1473
    if (SIXEL_FAILED(status)) {
×
1474
        goto end;
×
1475
    }
1476

1477
    status = sixel_loader_setopt(loader,
×
1478
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1479
                                 &fuse_palette);
1480
    if (SIXEL_FAILED(status)) {
×
1481
        goto end;
×
1482
    }
1483

1484
    status = sixel_loader_setopt(loader,
×
1485
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1486
                                 &reqcolors);
1487
    if (SIXEL_FAILED(status)) {
×
1488
        goto end;
×
1489
    }
1490

1491
    status = sixel_loader_setopt(loader,
×
1492
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1493
                                 bgcolor);
1494
    if (SIXEL_FAILED(status)) {
×
1495
        goto end;
×
1496
    }
1497

1498
    status = sixel_loader_setopt(loader,
×
1499
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1500
                                 &loop_control);
1501
    if (SIXEL_FAILED(status)) {
×
1502
        goto end;
×
1503
    }
1504

1505
    status = sixel_loader_setopt(loader,
×
1506
                                 SIXEL_LOADER_OPTION_INSECURE,
1507
                                 &finsecure);
1508
    if (SIXEL_FAILED(status)) {
×
1509
        goto end;
×
1510
    }
1511

1512
    status = sixel_loader_setopt(loader,
×
1513
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1514
                                 cancel_flag);
1515
    if (SIXEL_FAILED(status)) {
×
1516
        goto end;
×
1517
    }
1518

1519
    status = sixel_loader_setopt(loader,
×
1520
                                 SIXEL_LOADER_OPTION_CONTEXT,
1521
                                 context);
1522
    if (SIXEL_FAILED(status)) {
×
1523
        goto end;
×
1524
    }
1525

1526
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1527

UNCOV
1528
end:
×
1529
    sixel_loader_unref(loader);
×
1530

1531
    return status;
×
1532
}
1533

1534

1535
SIXELAPI size_t
1536
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
8✔
1537
{
1538
    sixel_loader_entry_t const *entries;
8✔
1539
    size_t entry_count;
8✔
1540
    size_t limit;
8✔
1541
    size_t index;
8✔
1542

1543
    entries = NULL;
8✔
1544
    entry_count = loader_registry_get_entries(&entries);
8✔
1545

1546
    if (names != NULL && max_names > 0) {
8!
1547
        limit = entry_count;
4✔
1548
        if (limit > max_names) {
4!
1549
            limit = max_names;
1550
        }
1551
        for (index = 0; index < limit; ++index) {
12✔
1552
            names[index] = entries[index].name;
8✔
1553
        }
1554
    }
1555

1556
    return entry_count;
8✔
1557
}
1558

1559
#if HAVE_TESTS
1560
/* Simple allocation smoke test to exercise loader test harness entry. */
1561
static int
1562
loader_test_allocation_smoke(void)
1563
{
1564
    int nret = EXIT_FAILURE;
1565
    unsigned char *ptr = malloc(16);
1566

1567
    nret = EXIT_SUCCESS;
1568
    goto error;
1569

1570
error:
1571
    free(ptr);
1572
    return nret;
1573
}
1574

1575

1576
SIXELAPI int
1577
sixel_loader_tests_main(void)
×
1578
{
1579
    int nret = EXIT_FAILURE;
×
1580
    size_t i;
×
UNCOV
1581
    typedef int (* testcase)(void);
×
1582

UNCOV
1583
    static testcase const testcases[] = {
×
1584
        loader_test_allocation_smoke,
1585
    };
1586

1587
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
1588
        nret = testcases[i]();
×
1589
        if (nret != EXIT_SUCCESS) {
×
1590
            goto error;
1591
        }
1592
    }
1593

1594
    nret = EXIT_SUCCESS;
1595

UNCOV
1596
error:
×
1597
    return nret;
×
1598
}
1599
#endif  /* HAVE_TESTS */
1600

1601
/* emacs Local Variables:      */
1602
/* emacs mode: c               */
1603
/* emacs tab-width: 4          */
1604
/* emacs indent-tabs-mode: nil */
1605
/* emacs c-basic-offset: 4     */
1606
/* emacs End:                  */
1607
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1608
/* 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