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

saitoha / libsixel / 23380678255

21 Mar 2026 01:24PM UTC coverage: 81.547% (-0.08%) from 81.627%
23380678255

push

github

saitoha
loader: share -L schema and build plan from parsed AST

28940 of 68810 branches covered (42.06%)

53 of 60 new or added lines in 4 files covered. (88.33%)

1438 existing lines in 8 files now uncovered.

55183 of 67670 relevant lines covered (81.55%)

4093439.68 hits per line

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

86.89
/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
#include <sixel.h>
95
#include "loader.h"
96
#include "loader-builtin.h"
97
#include "loader-common.h"
98
#include "loader-coregraphics.h"
99
#include "loader-gd.h"
100
#include "loader-gdk-pixbuf2.h"
101
#include "loader-gnome-thumbnailer.h"
102
#include "loader-libjpeg.h"
103
#include "loader-libpng.h"
104
#include "loader-librsvg.h"
105
#include "loader-quicklook.h"
106
#include "loader-registry.h"
107
#include "loader-factory.h"
108
#include "loader-manager.h"
109
#include "loader-component.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 "encoder.h"
116
#include "logger.h"
117
#include "options.h"
118
#include "sixel_atomic.h"
119

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

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

157
typedef struct sixel_loader_component_option_context {
158
    sixel_loader_t *loader;
159
    int reqcolors;
160
} sixel_loader_component_option_context_t;
161

162
typedef struct sixel_loader_manager_trace_context {
163
    sixel_loader_t *loader;
164
    size_t input_bytes;
165
} sixel_loader_manager_trace_context_t;
166

167
int
168
sixel_loader_callback_is_canceled(void *data)
42,526✔
169
{
170
    sixel_loader_callback_state_t *state;
27,326✔
171

172
    state = (sixel_loader_callback_state_t *)data;
42,526✔
173
    if (state == NULL || state->loader == NULL ||
42,526!
174
        state->loader->cancel_flag == NULL) {
42,156!
175
        return 0;
7,553✔
176
    }
177

178
    return *state->loader->cancel_flag != 0;
31,565✔
179
}
26,050✔
180

181

182
#if HAVE_POSIX_SPAWNP
183
extern char **environ;
184
#endif
185

186
static char *
187
loader_strdup(char const *text, sixel_allocator_t *allocator)
3,637✔
188
{
189
    char *copy;
2,262✔
190
    size_t length;
2,262✔
191

192
    if (text == NULL) {
3,637!
193
        return NULL;
194
    }
195

196
    length = strlen(text) + 1;
3,637✔
197
    copy = (char *)sixel_allocator_malloc(allocator, length);
3,637✔
198
    if (copy == NULL) {
3,637!
199
        return NULL;
200
    }
201

202
    /* Copy the terminating NUL byte as part of length. */
203
    memcpy(copy, text, length);
3,637✔
204

205
    return copy;
3,637✔
206
}
2,770✔
207

208

209

210
/*
211
 * Emit loader stage markers.
212
 *
213
 * Loader callbacks run the downstream pipeline synchronously, so the finish
214
 * marker must be issued before invoking fn_load() to avoid inflating the
215
 * loader span. The helper keeps the formatting consistent with
216
 * sixel_encoder_log_stage() without depending on encoder internals.
217
 */
218
static void
219
loader_log_stage(sixel_loader_t *loader,
39,406✔
220
                 char const *event,
221
                 char const *fmt,
222
                 ...)
223
{
224
    sixel_logger_t *logger;
24,656✔
225
    char message[256];
24,656✔
226
    va_list args;
24,656✔
227

228
    logger = NULL;
39,406✔
229
    if (loader != NULL) {
39,406!
230
        logger = &loader->logger;
39,406✔
231
    }
24,559✔
232
    if (logger == NULL || logger->file == NULL || !logger->active) {
39,406!
233
        return;
39,406✔
234
    }
235

236
    message[0] = '\0';
×
237
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
238
# if defined(__clang__)
239
#  pragma clang diagnostic push
240
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
241
# elif defined(__GNUC__) && !defined(__PCC__)
242
#  pragma GCC diagnostic push
243
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
244
# endif
245
#endif
246
    va_start(args, fmt);
×
247
    if (fmt != NULL) {
×
248
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
249
# if defined(__clang__)
250
#  pragma clang diagnostic push
251
#  pragma clang diagnostic ignored "-Wformat-nonliteral"
252
# elif defined(__GNUC__) && !defined(__PCC__)
253
#  pragma GCC diagnostic push
254
#  pragma GCC diagnostic ignored "-Wformat-nonliteral"
255
# endif
256
#endif
257
        (void)sixel_compat_vsnprintf(message, sizeof(message), fmt, args);
×
258
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
259
# if defined(__clang__)
260
#  pragma clang diagnostic pop
261
# elif defined(__GNUC__) && !defined(__PCC__)
262
#  pragma GCC diagnostic pop
263
# endif
264
#endif
265
    }
266
    va_end(args);
×
267
#if HAVE_DIAGNOSTIC_FORMAT_NONLITERAL
268
# if defined(__clang__)
269
#  pragma clang diagnostic pop
270
# elif defined(__GNUC__) && !defined(__PCC__)
271
#  pragma GCC diagnostic pop
272
# endif
273
#endif
274

275
    sixel_logger_logf(logger,
×
276
                      "worker",
277
                      "loader",
278
                      event,
279
                      -1,
280
                      -1,
281
                      0,
282
                      0,
283
                      0,
284
                      0,
285
                      "%s",
286
                      message);
287
}
24,559!
288

289
static SIXELSTATUS
290
loader_callback_trampoline(sixel_frame_t *frame, void *data)
21,088✔
291
{
292
    sixel_loader_callback_state_t *state;
13,218✔
293
    SIXELSTATUS status;
13,218✔
294
    sixel_loader_t *loader;
13,218✔
295

296
    state = (sixel_loader_callback_state_t *)data;
21,088✔
297
    loader = NULL;
21,088✔
298
    if (state == NULL || state->fn == NULL) {
21,088!
299
        return SIXEL_BAD_ARGUMENT;
300
    }
301

302
    loader = state->loader;
21,088✔
303
    if (loader != NULL && loader->log_loader_finished == 0) {
21,088!
304
        loader_log_stage(loader,
31,567✔
305
                         "finish",
306
                         "path=%s loader=%s bytes=%zu",
307
                         loader->log_path,
19,457✔
308
                         loader->log_loader_name,
19,457✔
309
                         loader->log_input_bytes);
12,110✔
310
        loader->log_loader_finished = 1;
19,457✔
311
    }
12,110✔
312

313
    status = state->fn(frame, state->context);
21,088✔
314
    if (SIXEL_FAILED(status) && state->loader != NULL) {
21,088!
315
        state->loader->callback_failed = 1;
41✔
316
    }
25✔
317

318
    return status;
15,868✔
319
}
13,180✔
320

321

322
static SIXELSTATUS
323
loader_apply_component_options(sixel_loader_component_t *component,
20,086✔
324
                               sixel_loader_t const *loader,
325
                               int reqcolors)
326
{
327
    typedef struct loader_component_option_entry {
4,966✔
328
        int option;
329
        char const *name;
330
    } loader_component_option_entry_t;
331

332
    loader_component_option_entry_t const options[] = {
20,086✔
333
        { SIXEL_LOADER_OPTION_REQUIRE_STATIC, "require-static" },
334
        { SIXEL_LOADER_OPTION_USE_PALETTE, "use-palette" },
335
        { SIXEL_LOADER_OPTION_REQCOLORS, "reqcolors" },
336
        { SIXEL_LOADER_OPTION_BGCOLOR, "bgcolor" },
337
        { SIXEL_LOADER_OPTION_LOOP_CONTROL, "loop-control" },
338
        { SIXEL_LOADER_OPTION_START_FRAME_NO, "start-frame-no" }
339
    };
340
    void const *value;
12,535✔
341
    char message[128];
12,535✔
342
    size_t index;
12,535✔
343
    SIXELSTATUS status;
12,535✔
344

345
    /*
346
     * Distribute common execution parameters to every loader component.
347
     *
348
     * +---------------------------+-------------------------------+
349
     * | option                    | value source                  |
350
     * +---------------------------+-------------------------------+
351
     * | REQUIRE_STATIC            | loader->fstatic               |
352
     * | USE_PALETTE               | loader->fuse_palette          |
353
     * | REQCOLORS                 | normalized reqcolors          |
354
     * | BGCOLOR                   | loader->bgcolor or NULL       |
355
     * | LOOP_CONTROL              | loader->loop_control          |
356
     * | START_FRAME_NO            | loader->start_frame_no/NULL   |
357
     * +---------------------------+-------------------------------+
358
     */
359
    status = SIXEL_OK;
20,086✔
360
    message[0] = '\0';
20,086✔
361
    index = 0;
20,086✔
362
    value = NULL;
20,086✔
363
    if (component == NULL || loader == NULL) {
20,086!
364
        return SIXEL_BAD_ARGUMENT;
365
    }
366

367
    for (index = 0; index < sizeof(options) / sizeof(options[0]); ++index) {
140,602!
368
        switch (options[index].option) {
120,516!
369
        case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
7,537!
370
            value = &loader->fstatic;
20,086✔
371
            break;
20,086✔
372
        case SIXEL_LOADER_OPTION_USE_PALETTE:
7,537!
373
            value = &loader->fuse_palette;
20,086✔
374
            break;
20,086✔
375
        case SIXEL_LOADER_OPTION_REQCOLORS:
2,571!
376
            value = &reqcolors;
15,120✔
377
            break;
15,120✔
378
        case SIXEL_LOADER_OPTION_BGCOLOR:
7,537!
379
            value = loader->has_bgcolor ? loader->bgcolor : NULL;
20,086✔
380
            break;
15,120✔
381
        case SIXEL_LOADER_OPTION_LOOP_CONTROL:
7,537!
382
            value = &loader->loop_control;
20,086✔
383
            break;
20,086✔
384
        case SIXEL_LOADER_OPTION_START_FRAME_NO:
7,537!
385
            value = loader->has_start_frame_no
16,686✔
386
                ? &loader->start_frame_no : NULL;
7,686✔
387
            break;
15,120✔
388
        default:
×
389
            value = NULL;
9,870✔
390
            break;
391
        }
392

393
        status = sixel_loader_component_setopt(component,
195,810✔
394
                                               options[index].option,
90,720✔
395
                                               value);
75,294✔
396
        if (SIXEL_FAILED(status)) {
120,516!
397
            (void)sixel_compat_snprintf(message,
×
398
                                        sizeof(message),
399
                                        "sixel_loader_load_file: "
400
                                        "failed to apply loader option "
401
                                        "'%s'.",
402
                                        options[index].name);
×
403
            sixel_helper_set_additional_message(
×
404
                message);
405
            return status;
×
406
        }
407
    }
75,294✔
408

409
    return SIXEL_OK;
15,120✔
410
}
12,549✔
411

412

413
static void
414
loader_append_chunk(char *dest,
813✔
415
                    size_t capacity,
416
                    size_t *offset,
417
                    char const *chunk)
418
{
419
    size_t available;
624✔
420
    size_t length;
624✔
421

422
    if (dest == NULL || offset == NULL || chunk == NULL) {
813!
423
        return;
424
    }
425

426
    if (*offset >= capacity) {
813!
427
        return;
428
    }
429

430
    available = capacity - *offset;
813✔
431
    if (available == 0) {
813!
432
        return;
433
    }
434

435
    length = strlen(chunk);
813✔
436
    if (length >= available) {
813!
437
        if (available == 0) {
×
438
            return;
439
        }
440
        length = available - 1u;
×
441
    }
442

443
    if (length > 0) {
813!
444
        memcpy(dest + *offset, chunk, length);
813✔
445
        *offset += length;
813✔
446
    }
813✔
447

448
    if (*offset < capacity) {
813!
449
        dest[*offset] = '\0';
813✔
450
    } else {
813✔
451
        dest[capacity - 1u] = '\0';
×
452
    }
453
}
813!
454

455
static void
456
loader_append_key_value(char *dest,
401✔
457
                        size_t capacity,
458
                        size_t *offset,
459
                        char const *label,
460
                        char const *value)
461
{
462
    char line[128];
300✔
463
    int written;
300✔
464

465
    if (value == NULL || value[0] == '\0') {
401!
466
        return;
×
467
    }
468

469
    written = sixel_compat_snprintf(line,
802✔
470
                                    sizeof(line),
471
                                    "  %-10s: %s\n",
472
                                    label,
401✔
473
                                    value);
401✔
474
    if (written < 0) {
401!
475
        return;
476
    }
477

478
    if ((size_t)written >= sizeof(line)) {
401!
479
        line[sizeof(line) - 1u] = '\0';
×
480
    }
481

482
    loader_append_chunk(dest, capacity, offset, line);
401✔
483
}
401!
484

485
static void
486
loader_extract_extension(char const *path, char *buffer, size_t capacity)
103✔
487
{
488
    char const *dot;
81✔
489
    size_t index;
81✔
490

491
    if (buffer == NULL || capacity == 0) {
103!
492
        return;
493
    }
494

495
    buffer[0] = '\0';
103✔
496

497
    if (path == NULL) {
103!
498
        return;
499
    }
500

501
    dot = strrchr(path, '.');
103✔
502
    if (dot == NULL || dot[1] == '\0') {
103!
503
        return;
23✔
504
    }
505

506
#if defined(_WIN32)
507
    {
508
        char const *slash;
509
        char const *backslash;
510

511
        slash = strrchr(path, '/');
512
        backslash = strrchr(path, '\\');
513
        if ((slash != NULL && dot < slash) ||
514
                (backslash != NULL && dot < backslash)) {
515
            return;
516
        }
517
    }
518
#else
519
    {
520
        char const *slash;
61✔
521

522
        slash = strrchr(path, '/');
80✔
523
        if (slash != NULL && dot < slash) {
80!
524
            return;
525
        }
526
    }
61!
527
#endif
528

529
    if (dot[1] == '\0') {
80!
530
        return;
531
    }
532

533
    dot += 1;
80✔
534

535
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
338!
536
        buffer[index] = (char)tolower((unsigned char)dot[index]);
258✔
537
    }
258✔
538
    buffer[index] = '\0';
80✔
539
}
103!
540

541

542

543

544

545

546

547

548

549

550

551
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
552
static void
553
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
38✔
554
{
555
    if (buffer == NULL || capacity == 0) {
38!
556
        return;
557
    }
558

559
    buffer[0] = '\0';
38✔
560
    if (source == NULL) {
38!
561
        return;
562
    }
563

564
    if (!CFStringGetCString(source,
76!
565
                             buffer,
38✔
566
                             (CFIndex)capacity,
38✔
567
                             kCFStringEncodingUTF8)) {
568
        buffer[0] = '\0';
569
    }
570
}
38✔
571
#endif
572

573

574
static void
575
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
103✔
576
                          char const *filename)
577
{
578
    enum { description_length = 128 };
579
    enum { uttype_length = 128 };
580
    enum { extension_length = 32 };
581
    enum { message_length = 768 };
582
    char message[message_length];
81✔
583
    char type_value[description_length];
81✔
584
    char extension_text[extension_length + 2];
81✔
585
    char uttype[uttype_length];
81✔
586
    char desc_buffer[description_length];
81✔
587
    char extension[extension_length];
81✔
588
    char const *path;
81✔
589
    char const *display_path;
81✔
590
    char const *metadata_path;
81✔
591
    char const *description_text;
81✔
592
    char *mime_string;
81✔
593
    char *description_string;
81✔
594
    size_t offset;
81✔
595
    int gnome_available;
81✔
596
    int gnome_has_dirs;
81✔
597
    int gnome_has_match;
81✔
598
    int suggestions;
81✔
599

600
    message[0] = '\0';
103✔
601
    type_value[0] = '\0';
103✔
602
    extension_text[0] = '\0';
103✔
603
    uttype[0] = '\0';
103✔
604
    desc_buffer[0] = '\0';
103✔
605
    extension[0] = '\0';
103✔
606
    path = NULL;
103✔
607
    display_path = "(stdin)";
103✔
608
    metadata_path = NULL;
103✔
609
    description_text = NULL;
103✔
610
    mime_string = NULL;
103✔
611
    description_string = NULL;
103✔
612
    offset = 0u;
103✔
613
    gnome_available = 0;
103✔
614
    gnome_has_dirs = 0;
103✔
615
    gnome_has_match = 0;
103✔
616
    suggestions = 0;
103✔
617

618
    if (pchunk != NULL && pchunk->source_path != NULL) {
103!
619
        path = pchunk->source_path;
96✔
620
    } else if (filename != NULL) {
103!
621
        path = filename;
7✔
622
    }
7✔
623

624
    if (path != NULL && strcmp(path, "-") != 0) {
103!
625
        display_path = path;
96✔
626
    }
96✔
627

628
    if (path != NULL && strcmp(path, "-") != 0 &&
103!
629
            strstr(path, "://") == NULL) {
96!
630
        metadata_path = path;
96✔
631
    }
96✔
632

633
    loader_extract_extension(path, extension, sizeof(extension));
103✔
634

635
#if HAVE_FREEDESKTOP_THUMBNAILING
636
    if (metadata_path != NULL) {
103!
637
        /*
638
         * Collect MIME metadata via file(1) when fork() and friends are
639
         * available.  Windows builds compiled with clang64 lack these
640
         * interfaces, so the thumbnail helpers remain disabled there.
641
         */
642
        mime_string = thumbnailer_guess_content_type(metadata_path);
96✔
643
        description_string = thumbnailer_run_file(metadata_path, NULL);
96✔
644
    }
96✔
645
#else
646
    (void)metadata_path;
647
#endif
648

649
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
650
#if defined(__clang__)
651
    /*
652
     * Allow use of legacy UTType C APIs when compiling with the
653
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
654
     * so we must intentionally silence the deprecation warnings here.
655
     */
656
#pragma clang diagnostic push
657
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
658
#endif
659
    {
660
        CFStringRef uti_ref;
661
        CFStringRef mime_ref;
662
        CFStringRef ext_ref;
663
        CFStringRef desc_ref;
664
        CFStringRef preferred_mime;
665
        char uti_local[uttype_length];
666
        char desc_local[description_length];
667
        char mime_local[64];
668

669
        uti_ref = NULL;
22✔
670
        mime_ref = NULL;
22✔
671
        ext_ref = NULL;
22✔
672
        desc_ref = NULL;
22✔
673
        preferred_mime = NULL;
22✔
674
        uti_local[0] = '\0';
22✔
675
        desc_local[0] = '\0';
22✔
676
        mime_local[0] = '\0';
22✔
677

678
        if (mime_string != NULL) {
22✔
679
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
38✔
680
                                                 mime_string,
19✔
681
                                                 kCFStringEncodingUTF8);
682
        }
19✔
683
        if (mime_ref != NULL) {
22✔
684
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
19✔
685
                kUTTagClassMIMEType,
19✔
686
                mime_ref,
19✔
687
                NULL);
688
        }
19✔
689
        if (uti_ref == NULL && extension[0] != '\0') {
22!
690
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
691
                                                extension,
692
                                                kCFStringEncodingUTF8);
693
            if (ext_ref != NULL) {
×
694
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
695
                    kUTTagClassFilenameExtension,
696
                    ext_ref,
697
                    NULL);
698
            }
699
        }
700
        if (uti_ref != NULL) {
22✔
701
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
19✔
702
            desc_ref = UTTypeCopyDescription(uti_ref);
19✔
703
            if (desc_ref != NULL) {
19!
704
                loader_copy_cfstring(desc_ref,
38✔
705
                                     desc_local,
19✔
706
                                     sizeof(desc_local));
707
                CFRelease(desc_ref);
19✔
708
                desc_ref = NULL;
19✔
709
            }
19✔
710
            if (mime_string == NULL) {
19!
711
                preferred_mime = UTTypeCopyPreferredTagWithClass(
712
                    uti_ref,
713
                    kUTTagClassMIMEType);
714
                if (preferred_mime != NULL) {
×
715
                    loader_copy_cfstring(preferred_mime,
716
                                         mime_local,
717
                                         sizeof(mime_local));
718
                    CFRelease(preferred_mime);
719
                    preferred_mime = NULL;
720
                }
721
                if (mime_local[0] != '\0') {
×
722
                    mime_string = thumbnailer_strdup(mime_local);
723
                }
724
            }
725
        }
19✔
726
        if (mime_ref != NULL) {
22✔
727
            CFRelease(mime_ref);
19✔
728
        }
19✔
729
        if (ext_ref != NULL) {
22!
730
            CFRelease(ext_ref);
731
        }
732
        if (uti_ref != NULL) {
22✔
733
            CFRelease(uti_ref);
19✔
734
        }
19✔
735
        if (uti_local[0] != '\0') {
22✔
736
            sixel_compat_snprintf(uttype,
38✔
737
                                  sizeof(uttype),
738
                                  "%s",
739
                                  uti_local);
19✔
740
        }
19✔
741
        if (desc_local[0] != '\0') {
22✔
742
            sixel_compat_snprintf(desc_buffer,
38✔
743
                                  sizeof(desc_buffer),
744
                                  "%s",
745
                                  desc_local);
19✔
746
        }
19✔
747
    }
748
#if defined(__clang__)
749
#pragma clang diagnostic pop
750
#endif
751
#endif
752

753
    if (description_string != NULL && description_string[0] != '\0') {
103!
754
        description_text = description_string;
96✔
755
    } else if (desc_buffer[0] != '\0') {
103!
756
        description_text = desc_buffer;
757
    } else {
758
        description_text = "unknown content";
7✔
759
    }
760

761
    sixel_compat_snprintf(type_value,
206✔
762
                          sizeof(type_value),
763
                          "%s",
764
                          description_text);
103✔
765

766
    loader_append_chunk(message,
103✔
767
                        sizeof(message),
768
                        &offset,
769
                        "diagnostic:\n");
770
    loader_append_key_value(message,
206✔
771
                            sizeof(message),
772
                            &offset,
773
                            "file",
774
                            display_path);
103✔
775
    loader_append_key_value(message,
206✔
776
                            sizeof(message),
777
                            &offset,
778
                            "type",
779
                            type_value);
103✔
780

781
    if (mime_string != NULL && mime_string[0] != '\0') {
103!
782
        loader_append_key_value(message,
192✔
783
                                sizeof(message),
784
                                &offset,
785
                                "mime",
786
                                mime_string);
96✔
787
    }
96✔
788

789
    if (uttype[0] != '\0') {
103!
790
        loader_append_key_value(message,
38✔
791
                                sizeof(message),
792
                                &offset,
793
                                "uti",
794
                                uttype);
19✔
795
    }
19✔
796

797
    if (extension[0] != '\0') {
103!
798
        sixel_compat_snprintf(extension_text,
160✔
799
                              sizeof(extension_text),
800
                              ".%s",
801
                              extension);
80✔
802
        loader_append_key_value(message,
160✔
803
                                sizeof(message),
804
                                &offset,
805
                                "extension",
806
                                extension_text);
80✔
807
    }
80✔
808

809
    loader_append_chunk(message,
103✔
810
                        sizeof(message),
811
                        &offset,
812
                        "  suggestions:\n");
813

814
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
815
    int quicklook_available;
816
    int quicklook_supported;
817

818
    quicklook_available = 0;
22✔
819
    quicklook_supported = 0;
22✔
820

821
    quicklook_available = loader_registry_entry_available("quicklook");
22✔
822
    if (quicklook_available) {
22!
823
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
22✔
824
    }
22✔
825
    if (quicklook_supported) {
22✔
826
        loader_append_chunk(message,
3✔
827
                            sizeof(message),
828
                            &offset,
829
                            "    - QuickLook rendered a preview during "
830
                            "the probe; try -L quicklook.\n");
831
        suggestions += 1;
3✔
832
    }
3✔
833
#endif
834

835
#if HAVE_FREEDESKTOP_THUMBNAILING
836
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
103✔
837
    if (gnome_available) {
103!
838
        loader_probe_gnome_thumbnailers(mime_string,
103✔
839
                                        &gnome_has_dirs,
840
                                        &gnome_has_match);
841
        if (gnome_has_dirs && gnome_has_match) {
103!
842
            loader_append_chunk(message,
843
                                sizeof(message),
844
                                &offset,
845
                                "    - GNOME thumbnailer definitions match "
846
                                "this MIME type; try -L gnome-thumbnailer.\n"
847
                                );
848
            suggestions += 1;
849
        }
850
    }
103✔
851
#else
852
    (void)gnome_available;
853
    (void)gnome_has_dirs;
854
    (void)gnome_has_match;
855
#endif
856

857
    if (suggestions == 0) {
103✔
858
        loader_append_chunk(message,
100✔
859
                            sizeof(message),
860
                            &offset,
861
                            "    (no thumbnail helper hints)\n");
862
    }
100✔
863

864
    if (suggestions > 0) {
103✔
865
        loader_append_chunk(message,
3✔
866
                            sizeof(message),
867
                            &offset,
868
                            "  hint       : Enable one of the suggested "
869
                            "loaders with -L.\n");
870
    } else {
3✔
871
        loader_append_chunk(message,
100✔
872
                            sizeof(message),
873
                            &offset,
874
                            "  hint       : Convert the file to PNG or "
875
                            "enable optional loaders.\n");
876
    }
877

878
    sixel_helper_set_additional_message(message);
103✔
879

880
    free(mime_string);
103✔
881
    free(description_string);
103✔
882
}
103✔
883

884
SIXELAPI SIXELSTATUS
885
sixel_loader_new(
19,949✔
886
    sixel_loader_t   /* out */ **pploader,
887
    sixel_allocator_t/* in */  *allocator)
888
{
889
    SIXELSTATUS status = SIXEL_FALSE;
19,949✔
890
    sixel_loader_t *loader;
12,478✔
891
    sixel_allocator_t *local_allocator;
12,478✔
892

893
    loader = NULL;
19,949✔
894
    local_allocator = allocator;
19,949✔
895

896
    if (pploader == NULL) {
19,949!
897
        sixel_helper_set_additional_message(
×
898
            "sixel_loader_new: pploader is null.");
899
        status = SIXEL_BAD_ARGUMENT;
×
900
        goto end;
×
901
    }
902

903
    if (local_allocator == NULL) {
19,949!
904
        status = sixel_allocator_new(&local_allocator,
×
905
                                     NULL,
906
                                     NULL,
907
                                     NULL,
908
                                     NULL);
909
        if (SIXEL_FAILED(status)) {
×
910
            goto end;
×
911
        }
912
    } else {
913
        sixel_allocator_ref(local_allocator);
19,949✔
914
    }
915

916
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
19,949✔
917
                                                      sizeof(*loader));
918
    if (loader == NULL) {
19,949!
919
        sixel_helper_set_additional_message(
×
920
            "sixel_loader_new: sixel_allocator_malloc() failed.");
921
        status = SIXEL_BAD_ALLOCATION;
×
922
        sixel_allocator_unref(local_allocator);
×
923
        goto end;
×
924
    }
925

926
    loader->ref = 1U;
19,949✔
927
    loader->fstatic = 0;
19,949✔
928
    /*
929
     * Default policy: keep source palettes when a loader can provide them.
930
     * This avoids unnecessary requantization loss in downstream encoding.
931
     */
932
    loader->fuse_palette = 1;
19,949✔
933
    loader->reqcolors = SIXEL_PALETTE_MAX;
19,949✔
934
    loader->bgcolor[0] = 0;
19,949✔
935
    loader->bgcolor[1] = 0;
19,949✔
936
    loader->bgcolor[2] = 0;
19,949✔
937
    loader->has_bgcolor = 0;
19,949✔
938
    loader->loop_control = SIXEL_LOOP_AUTO;
19,949✔
939
    loader->finsecure = 0;
19,949✔
940
    loader->has_start_frame_no = 0;
19,949✔
941
    loader->start_frame_no = INT_MIN;
19,949✔
942
    loader->cancel_flag = NULL;
19,949✔
943
    loader->context = NULL;
19,949✔
944
    /*
945
     * Initialize a private logger. The helper reuses an existing global
946
     * logger sink when present so loader markers share the timeline with
947
     * upstream stages without requiring sixel_loader_setopt().
948
     */
949
    sixel_logger_init(&loader->logger);
19,949✔
950
    (void)sixel_logger_prepare_env(&loader->logger);
19,949✔
951
    loader->loader_order = NULL;
19,949✔
952
    loader->allocator = local_allocator;
19,949✔
953
    loader->last_loader_name[0] = '\0';
19,949✔
954
    loader->last_source_path[0] = '\0';
19,949✔
955
    loader->last_input_bytes = 0u;
19,949✔
956
    loader->callback_failed = 0;
19,949✔
957
    loader->log_loader_finished = 0;
19,949✔
958
    loader->log_path[0] = '\0';
19,949✔
959
    loader->log_loader_name[0] = '\0';
19,949✔
960
    loader->log_input_bytes = 0u;
19,949✔
961

962
    *pploader = loader;
19,949✔
963
    status = SIXEL_OK;
19,949✔
964

965
end:
7,500✔
966
    return status;
27,483✔
967
}
7,534✔
968

969
SIXELAPI void
970
sixel_loader_ref(
179,039✔
971
    sixel_loader_t /* in */ *loader)
972
{
973
    if (loader == NULL) {
179,039!
974
        return;
975
    }
976

977
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
179,039✔
978
}
111,371✔
979

980
SIXELAPI void
981
sixel_loader_unref(
198,988✔
982
    sixel_loader_t /* in */ *loader)
983
{
984
    sixel_allocator_t *allocator;
124,544✔
985
    unsigned int previous;
124,544✔
986

987
    if (loader == NULL) {
198,988!
988
        return;
989
    }
990

991
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
198,988✔
992
    if (previous == 1U) {
198,988✔
993
        allocator = loader->allocator;
19,949✔
994
        sixel_logger_close(&loader->logger);
19,949✔
995
        sixel_allocator_free(allocator, loader->loader_order);
19,949✔
996
        sixel_allocator_free(allocator, loader);
19,949✔
997
        sixel_allocator_unref(allocator);
19,949✔
998
    }
12,449✔
999
}
123,820!
1000

1001
SIXELAPI SIXELSTATUS
1002
sixel_loader_setopt(
159,090✔
1003
    sixel_loader_t /* in */ *loader,
1004
    int            /* in */ option,
1005
    void const     /* in */ *value)
1006
{
1007
    SIXELSTATUS status = SIXEL_FALSE;
159,090✔
1008
    int const *flag;
99,588✔
1009
    unsigned char const *color;
99,588✔
1010
    char const *order;
99,588✔
1011
    char *copy;
99,588✔
1012
    sixel_allocator_t *allocator;
99,588✔
1013

1014
    flag = NULL;
159,090✔
1015
    color = NULL;
159,090✔
1016
    order = NULL;
159,090✔
1017
    copy = NULL;
159,090✔
1018
    allocator = NULL;
159,090✔
1019

1020
    if (loader == NULL) {
159,090!
1021
        sixel_helper_set_additional_message(
×
1022
            "sixel_loader_setopt: loader is null.");
1023
        status = SIXEL_BAD_ARGUMENT;
×
1024
        goto end0;
×
1025
    }
1026

1027
    sixel_loader_ref(loader);
159,090✔
1028

1029
    switch (option) {
159,090!
1030
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
7,500!
1031
        flag = (int const *)value;
19,949✔
1032
        loader->fstatic = flag != NULL ? *flag : 0;
19,949!
1033
        status = SIXEL_OK;
19,949✔
1034
        break;
19,949✔
1035
    case SIXEL_LOADER_OPTION_USE_PALETTE:
7,500!
1036
        flag = (int const *)value;
19,949✔
1037
        loader->fuse_palette = flag != NULL ? *flag : 1;
19,949!
1038
        status = SIXEL_OK;
19,949✔
1039
        break;
19,949✔
1040
    case SIXEL_LOADER_OPTION_REQCOLORS:
7,500!
1041
        flag = (int const *)value;
19,949✔
1042
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
19,949!
1043
        if (loader->reqcolors < 1) {
19,949!
1044
            sixel_helper_set_additional_message(
×
1045
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1046
            status = SIXEL_BAD_ARGUMENT;
×
1047
            goto end;
×
1048
        }
1049
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
19,949!
1050
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1051
        }
1052
        status = SIXEL_OK;
15,005✔
1053
        break;
15,005✔
1054
    case SIXEL_LOADER_OPTION_BGCOLOR:
3,792!
1055
        if (value == NULL) {
9,849✔
1056
            loader->has_bgcolor = 0;
9,657✔
1057
        } else {
5,919✔
1058
            color = (unsigned char const *)value;
192✔
1059
            loader->bgcolor[0] = color[0];
192✔
1060
            loader->bgcolor[1] = color[1];
192✔
1061
            loader->bgcolor[2] = color[2];
192✔
1062
            loader->has_bgcolor = 1;
192✔
1063
        }
1064
        status = SIXEL_OK;
7,341✔
1065
        break;
7,341✔
1066
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
7,500!
1067
        flag = (int const *)value;
19,949✔
1068
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
19,949!
1069
        status = SIXEL_OK;
19,949✔
1070
        break;
19,949✔
1071
    case SIXEL_LOADER_OPTION_INSECURE:
7,500!
1072
        flag = (int const *)value;
19,949✔
1073
        loader->finsecure = flag != NULL ? *flag : 0;
19,949!
1074
        status = SIXEL_OK;
19,949✔
1075
        break;
19,949✔
1076
    case SIXEL_LOADER_OPTION_START_FRAME_NO:
3,792!
1077
        if (value == NULL) {
9,849✔
1078
            loader->has_start_frame_no = 0;
9,656✔
1079
            loader->start_frame_no = INT_MIN;
9,656✔
1080
        } else {
5,908✔
1081
            flag = (int const *)value;
193✔
1082
            loader->start_frame_no = *flag;
193✔
1083
            loader->has_start_frame_no = 1;
193✔
1084
        }
1085
        status = SIXEL_OK;
7,341✔
1086
        break;
7,341✔
1087
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
3,792!
1088
        loader->cancel_flag = (int const *)value;
9,849✔
1089
        status = SIXEL_OK;
9,849✔
1090
        break;
9,849✔
1091
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
3,792!
1092
        allocator = loader->allocator;
9,849✔
1093
        sixel_allocator_free(allocator, loader->loader_order);
9,849✔
1094
        loader->loader_order = NULL;
9,849✔
1095
        if (value != NULL) {
9,849✔
1096
            order = (char const *)value;
3,637✔
1097
            copy = loader_strdup(order, allocator);
3,637✔
1098
            if (copy == NULL) {
3,637!
1099
                sixel_helper_set_additional_message(
×
1100
                    "sixel_loader_setopt: loader_strdup() failed.");
1101
                status = SIXEL_BAD_ALLOCATION;
×
1102
                goto end;
×
1103
            }
1104
            loader->loader_order = copy;
3,637✔
1105
        }
2,770✔
1106
        status = SIXEL_OK;
7,341✔
1107
        break;
7,341✔
1108
    case SIXEL_LOADER_OPTION_CONTEXT:
7,500!
1109
        loader->context = (void *)value;
19,949✔
1110
        status = SIXEL_OK;
19,949✔
1111
        break;
19,949✔
1112
    default:
×
1113
        sixel_helper_set_additional_message(
×
1114
            "sixel_loader_setopt: unknown option.");
1115
        status = SIXEL_BAD_ARGUMENT;
×
1116
        goto end;
×
1117
    }
98,922✔
1118

1119
end:
60,168✔
1120
    sixel_loader_unref(loader);
159,090✔
1121

1122
end0:
60,168✔
1123
    return status;
218,982✔
1124
}
59,892✔
1125

1126
SIXELAPI char const *
1127
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
17,962✔
1128
{
1129
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
17,962!
1130
        return NULL;
1131
    }
1132
    return loader->last_loader_name;
17,962✔
1133
}
11,016✔
1134

1135
SIXELAPI char const *
1136
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
17,656✔
1137
{
1138
    if (loader == NULL || loader->last_source_path[0] == '\0') {
17,656!
1139
        return NULL;
216✔
1140
    }
1141
    return loader->last_source_path;
17,350✔
1142
}
10,848✔
1143

1144
SIXELAPI size_t
1145
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
8,981✔
1146
{
1147
    if (loader == NULL) {
8,981!
1148
        return 0u;
1149
    }
1150
    return loader->last_input_bytes;
8,981✔
1151
}
5,508✔
1152

1153
int
1154
sixel_loader_get_start_frame_no(sixel_loader_t const *loader,
1155
                                int *start_frame_no)
1156
{
1157
    if (start_frame_no != NULL) {
×
1158
        *start_frame_no = INT_MIN;
×
1159
    }
1160
    if (loader == NULL || start_frame_no == NULL ||
×
1161
        loader->has_start_frame_no == 0) {
×
1162
        return 0;
1163
    }
1164

1165
    *start_frame_no = loader->start_frame_no;
×
1166
    return 1;
×
1167
}
1168

1169
static SIXELSTATUS
1170
loader_manager_configure_component(sixel_loader_component_t *component,
20,086✔
1171
                                   void *context)
1172
{
1173
    sixel_loader_component_option_context_t *options;
12,535✔
1174

1175
    options = (sixel_loader_component_option_context_t *)context;
20,086✔
1176
    if (options == NULL || options->loader == NULL) {
20,086!
1177
        sixel_helper_set_additional_message(
×
1178
            "loader_manager_configure_component: invalid context.");
1179
        return SIXEL_BAD_ARGUMENT;
×
1180
    }
1181

1182
    return loader_apply_component_options(component,
32,635✔
1183
                                          options->loader,
15,120✔
1184
                                          options->reqcolors);
12,549✔
1185
}
12,549✔
1186

1187
static void
1188
loader_manager_trace_try_callback(char const *name, void *context)
20,086✔
1189
{
1190
    sixel_loader_manager_trace_context_t *trace;
12,535✔
1191

1192
    trace = (sixel_loader_manager_trace_context_t *)context;
20,086✔
1193
    if (trace == NULL || trace->loader == NULL) {
20,086!
1194
        return;
1195
    }
1196

1197
    trace->loader->log_input_bytes = trace->input_bytes;
20,086✔
1198
    if (name != NULL) {
20,086!
1199
        (void)sixel_compat_snprintf(trace->loader->log_loader_name,
32,635✔
1200
                                    sizeof(trace->loader->log_loader_name),
1201
                                    "%s",
1202
                                    name);
12,549✔
1203
    } else {
12,549✔
1204
        trace->loader->log_loader_name[0] = '\0';
×
1205
    }
1206
    loader_trace_try(name);
20,086✔
1207
}
12,549!
1208

1209
static void
1210
loader_manager_trace_result_callback(char const *name,
20,086✔
1211
                                     SIXELSTATUS status,
1212
                                     void *context)
1213
{
1214
    (void)context;
17,515✔
1215
    loader_trace_result(name, status);
20,086✔
1216
}
20,086✔
1217

1218
SIXELAPI SIXELSTATUS
1219
sixel_loader_load_file(
19,949✔
1220
    sixel_loader_t         /* in */ *loader,
1221
    char const             /* in */ *filename,
1222
    sixel_load_image_function /* in */ fn_load)
1223
{
1224
    SIXELSTATUS status = SIXEL_FALSE;
19,949✔
1225
    sixel_chunk_t *pchunk;
12,478✔
1226
    sixel_loader_entry_t const **plan;
12,478✔
1227
    sixel_loader_entry_t const *entries;
12,478✔
1228
    sixel_loader_factory_t *factory;
12,478✔
1229
    sixel_loader_manager_t *manager;
12,478✔
1230
    sixel_loader_chain_t *chain;
12,478✔
1231
    size_t entry_count;
12,478✔
1232
    size_t plan_length;
12,478✔
1233
    int reqcolors;
12,478✔
1234
    char const *order_override;
12,478✔
1235
    char const *plan_order;
12,478✔
1236
    char const *env_order;
12,478✔
1237
    char const *selected_name;
12,478✔
1238
    sixel_loader_callback_state_t callback_state;
12,478✔
1239
    sixel_loader_component_option_context_t option_context;
12,478✔
1240
    sixel_loader_manager_trace_context_t trace_context;
12,478✔
1241
    sixel_option_argument_list_resolution_t order_resolution;
12,478✔
1242

1243
    pchunk = NULL;
19,949✔
1244
    plan = NULL;
19,949✔
1245
    entries = NULL;
19,949✔
1246
    factory = NULL;
19,949✔
1247
    manager = NULL;
19,949✔
1248
    chain = NULL;
19,949✔
1249
    entry_count = 0;
19,949✔
1250
    plan_length = 0;
19,949✔
1251
    reqcolors = 0;
19,949✔
1252
    order_override = NULL;
19,949✔
1253
    plan_order = NULL;
19,949✔
1254
    env_order = NULL;
19,949✔
1255
    selected_name = NULL;
19,949✔
1256
    order_resolution.canonical_argument = NULL;
19,949✔
1257
    order_resolution.has_trailing_bang = 0;
19,949✔
1258
    order_resolution.items = NULL;
19,949✔
1259
    order_resolution.item_count = 0u;
19,949✔
1260
    memset(&option_context, 0, sizeof(option_context));
19,949!
1261
    memset(&trace_context, 0, sizeof(trace_context));
19,949✔
1262

1263
    if (loader == NULL) {
19,949!
UNCOV
1264
        sixel_helper_set_additional_message(
×
1265
            "sixel_loader_load_file: loader is null.");
UNCOV
1266
        status = SIXEL_BAD_ARGUMENT;
×
UNCOV
1267
        goto end0;
×
1268
    }
1269

1270
    sixel_loader_ref(loader);
19,949✔
1271

1272
    loader->log_loader_finished = 0;
19,949✔
1273
    loader->log_loader_name[0] = '\0';
19,949✔
1274
    loader->log_input_bytes = 0u;
19,949✔
1275
    loader->log_path[0] = '\0';
19,949✔
1276
    if (filename != NULL) {
19,949✔
1277
        (void)sixel_compat_snprintf(loader->log_path,
31,963✔
1278
                                    sizeof(loader->log_path),
1279
                                    "%s",
1280
                                    filename);
12,299✔
1281
    }
12,299✔
1282
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
19,949✔
1283

1284
    memset(&callback_state, 0, sizeof(callback_state));
19,949✔
1285
    callback_state.loader = loader;
19,949✔
1286
    callback_state.fn = fn_load;
19,949✔
1287
    callback_state.context = loader->context;
19,949✔
1288
    loader->callback_failed = 0;
19,949✔
1289

1290
    status = loader_factory_get_default(&factory);
19,949✔
1291
    if (SIXEL_FAILED(status)) {
19,949!
UNCOV
1292
        goto end;
×
1293
    }
1294
    entry_count = loader_factory_get_entries(factory, &entries);
19,949✔
1295

1296
    reqcolors = loader->reqcolors;
19,949✔
1297
    if (reqcolors > SIXEL_PALETTE_MAX) {
19,949!
1298
        reqcolors = SIXEL_PALETTE_MAX;
1299
    }
1300

1301
    status = sixel_chunk_new(&pchunk,
19,949✔
1302
                             filename,
12,449✔
1303
                             loader->finsecure,
12,449✔
1304
                             loader->cancel_flag,
12,449✔
1305
                             loader->allocator);
12,449✔
1306
    if (status != SIXEL_OK) {
19,949!
1307
        goto end;
64✔
1308
    }
1309

1310
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
19,885!
UNCOV
1311
        status = SIXEL_OK;
×
UNCOV
1312
        goto end;
×
1313
    }
1314

1315
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
19,885!
1316
        (void)sixel_compat_snprintf(loader->log_path,
31,791✔
1317
                                    sizeof(loader->log_path),
1318
                                    "%s",
1319
                                    pchunk->source_path);
14,724✔
1320
    }
12,231✔
1321

1322
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
19,885!
UNCOV
1323
        status = SIXEL_LOGIC_ERROR;
×
UNCOV
1324
        goto end;
×
1325
    }
1326

1327
    status = SIXEL_FALSE;
19,885✔
1328
    order_override = loader->loader_order;
19,885✔
1329
    if (order_override == NULL) {
19,885✔
1330
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
16,248✔
1331
        if (env_order != NULL && env_order[0] != '\0') {
16,248!
1332
            order_override = env_order;
22✔
1333
        }
19✔
1334
    }
9,639✔
1335
    plan_order = order_override;
15,525✔
1336
    if (order_override != NULL && order_override[0] != '\0') {
15,525!
1337
        status = loader_manager_parse_loader_order(order_override,
3,667✔
1338
                                                   &order_resolution);
1339
        if (SIXEL_FAILED(status)) {
3,667✔
1340
            goto end;
19✔
1341
        }
1342
        plan_order = order_resolution.canonical_argument;
3,648✔
1343
    }
2,779✔
1344
    loader_manager_apply_loader_suboptions_resolution(&order_resolution);
19,866✔
1345

1346
    plan = sixel_allocator_malloc(loader->allocator,
35,649✔
1347
                                  entry_count * sizeof(*plan));
18,324✔
1348
    if (plan == NULL) {
19,866!
UNCOV
1349
        status = SIXEL_BAD_ALLOCATION;
×
1350
        goto end;
×
1351
    }
1352

1353
    if (order_override != NULL && order_override[0] != '\0') {
19,866!
1354
        plan_length = loader_manager_build_plan_from_resolution(&order_resolution,
3,648✔
1355
                                                                entries,
2,779✔
1356
                                                                entry_count,
2,779✔
1357
                                                                plan,
2,779✔
1358
                                                                entry_count);
2,779✔
1359
    } else {
2,779✔
1360
        plan_length = loader_manager_build_plan(plan_order,
25,838✔
1361
                                                entries,
9,620✔
1362
                                                entry_count,
9,620✔
1363
                                                plan,
9,620✔
1364
                                                entry_count);
9,620✔
1365
    }
1366
    if (plan_length == 0u) {
19,866!
1367
        if (plan_order != NULL && plan_order[0] != '\0') {
×
UNCOV
1368
            sixel_helper_set_additional_message(
×
1369
                "sixel_loader_load_file: no supported loader in loader "
1370
                "order.");
UNCOV
1371
            status = SIXEL_BAD_ARGUMENT;
×
1372
        } else {
UNCOV
1373
            sixel_helper_set_additional_message(
×
1374
                "sixel_loader_load_file: no available loader backend.");
UNCOV
1375
            status = SIXEL_LOADER_FAILED;
×
1376
        }
1377
        goto end;
×
1378
    }
1379

1380
    status = loader_manager_get_default(&manager);
19,866✔
1381
    if (SIXEL_FAILED(status)) {
19,866!
UNCOV
1382
        goto end;
×
1383
    }
1384

1385
    status = loader_manager_build_chain_from_plan(manager,
32,265✔
1386
                                                  plan,
12,399✔
1387
                                                  plan_length,
12,399✔
1388
                                                  pchunk,
12,399✔
1389
                                                  loader->allocator,
12,399✔
1390
                                                  &chain);
1391
    if (SIXEL_FAILED(status)) {
19,866!
UNCOV
1392
        goto end;
×
1393
    }
1394

1395
    option_context.loader = loader;
19,866✔
1396
    option_context.reqcolors = reqcolors;
19,866✔
1397
    trace_context.loader = loader;
19,866✔
1398
    trace_context.input_bytes = pchunk->size;
19,866✔
1399
    status = loader_manager_execute_chain(
19,866✔
1400
        manager,
12,399✔
1401
        chain,
12,399✔
1402
        pchunk,
12,399✔
1403
        loader_callback_trampoline,
1404
        &callback_state,
1405
        loader_manager_configure_component,
1406
        &option_context,
1407
        loader_manager_trace_try_callback,
1408
        loader_manager_trace_result_callback,
1409
        &trace_context,
1410
        &selected_name);
1411

1412
    if (SIXEL_FAILED(status)) {
19,866✔
1413
        if (status == SIXEL_FALSE) {
519!
1414
            if (!loader->callback_failed && pchunk != NULL) {
103!
1415
                status = SIXEL_LOADER_FAILED;
103✔
1416
                loader_publish_diagnostic(pchunk, filename);
103✔
1417
            } else {
103✔
1418
                sixel_helper_set_additional_message(
×
1419
                    "sixel_loader_load_file: loader returned "
1420
                    "unspecified failure.");
UNCOV
1421
                status = SIXEL_LOADER_FAILED;
×
1422
            }
1423
        }
103✔
1424
        goto end;
519✔
1425
    }
1426

1427
    if (selected_name != NULL) {
19,347!
1428
        (void)sixel_compat_snprintf(loader->last_loader_name,
31,387✔
1429
                                    sizeof(loader->last_loader_name),
1430
                                    "%s",
1431
                                    selected_name);
12,040✔
1432
    } else {
12,040✔
UNCOV
1433
        loader->last_loader_name[0] = '\0';
×
1434
    }
1435
    loader->last_input_bytes = pchunk->size;
19,347✔
1436
    if (pchunk->source_path != NULL) {
31,387✔
1437
        size_t path_len;
11,913✔
1438

1439
        path_len = strlen(pchunk->source_path);
19,041✔
1440
        if (path_len >= sizeof(loader->last_source_path)) {
19,041!
1441
            path_len = sizeof(loader->last_source_path) - 1u;
1442
        }
1443
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
19,041✔
1444
        loader->last_source_path[path_len] = '\0';
19,041✔
1445
    } else {
11,872✔
1446
        loader->last_source_path[0] = '\0';
306✔
1447
    }
1448

1449
end:
7,500✔
1450
    loader_chain_unref(chain);
19,949✔
1451
    chain = NULL;
19,949✔
1452
    loader_manager_unref(manager);
19,949✔
1453
    manager = NULL;
19,949✔
1454
    if (plan != NULL) {
19,949✔
1455
        sixel_allocator_free(loader->allocator, plan);
19,866✔
1456
        plan = NULL;
19,866✔
1457
    }
12,399✔
1458
    loader_factory_unref(factory);
19,949✔
1459
    factory = NULL;
19,949✔
1460
    sixel_chunk_destroy(pchunk);
19,949✔
1461
    sixel_option_free_argument_list_resolution(&order_resolution);
19,949✔
1462
    sixel_loader_unref(loader);
19,949✔
1463

1464
end0:
7,500✔
1465
    return status;
27,483✔
1466
}
7,534✔
1467

1468
/* load image from file */
1469

1470
SIXELAPI SIXELSTATUS
1471
sixel_helper_load_image_file(
1472
    char const                /* in */     *filename,     /* source file name */
1473
    int                       /* in */     fstatic,       /* whether to */
1474
                                                             /* extract a */
1475
                                                             /* static image */
1476
                                                             /* from an */
1477
                                                             /* animated gif */
1478
    int                       /* in */     fuse_palette,  /* whether to */
1479
                                                             /* use a */
1480
                                                             /* paletted */
1481
                                                             /* image; set */
1482
                                                             /* non-zero to */
1483
                                                             /* request one */
1484
    int                       /* in */     reqcolors,     /* requested */
1485
                                                             /* number of */
1486
                                                             /* colors; */
1487
                                                             /* should be */
1488
                                                             /* equal to or */
1489
                                                             /* less than */
1490
                                                             /* SIXEL_ */
1491
                                                             /* PALETTE_ */
1492
                                                             /* MAX */
1493
    unsigned char             /* in */     *bgcolor,      /* background */
1494
                                                             /* color, may */
1495
                                                             /* be NULL */
1496
    int                       /* in */     loop_control,  /* one of enum */
1497
                                                             /* loopControl */
1498
    sixel_load_image_function /* in */     fn_load,       /* callback */
1499
    int                       /* in */     finsecure,     /* true if do */
1500
                                                             /* not verify */
1501
                                                             /* SSL */
1502
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
1503
                                                             /* may be */
1504
                                                             /* NULL */
1505
    void                      /* in/out */ *context,      /* private data */
1506
                                                             /* passed to */
1507
                                                             /* callback */
1508
                                                             /* function, */
1509
                                                             /* may be */
1510
                                                             /* NULL */
1511
    sixel_allocator_t         /* in */     *allocator     /* allocator */
1512
                                                             /* object, */
1513
                                                             /* may be */
1514
                                                             /* NULL */
1515
)
1516
{
UNCOV
1517
    SIXELSTATUS status = SIXEL_FALSE;
×
1518
    sixel_loader_t *loader;
1519

UNCOV
1520
    loader = NULL;
×
1521

1522
    status = sixel_loader_new(&loader, allocator);
×
UNCOV
1523
    if (SIXEL_FAILED(status)) {
×
UNCOV
1524
        goto end;
×
1525
    }
1526

UNCOV
1527
    status = sixel_loader_setopt(loader,
×
1528
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1529
                                 &fstatic);
UNCOV
1530
    if (SIXEL_FAILED(status)) {
×
UNCOV
1531
        goto end;
×
1532
    }
1533

UNCOV
1534
    status = sixel_loader_setopt(loader,
×
1535
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1536
                                 &fuse_palette);
UNCOV
1537
    if (SIXEL_FAILED(status)) {
×
UNCOV
1538
        goto end;
×
1539
    }
1540

UNCOV
1541
    status = sixel_loader_setopt(loader,
×
1542
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1543
                                 &reqcolors);
UNCOV
1544
    if (SIXEL_FAILED(status)) {
×
UNCOV
1545
        goto end;
×
1546
    }
1547

UNCOV
1548
    status = sixel_loader_setopt(loader,
×
1549
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1550
                                 bgcolor);
UNCOV
1551
    if (SIXEL_FAILED(status)) {
×
UNCOV
1552
        goto end;
×
1553
    }
1554

UNCOV
1555
    status = sixel_loader_setopt(loader,
×
1556
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1557
                                 &loop_control);
UNCOV
1558
    if (SIXEL_FAILED(status)) {
×
UNCOV
1559
        goto end;
×
1560
    }
1561

UNCOV
1562
    status = sixel_loader_setopt(loader,
×
1563
                                 SIXEL_LOADER_OPTION_INSECURE,
1564
                                 &finsecure);
UNCOV
1565
    if (SIXEL_FAILED(status)) {
×
UNCOV
1566
        goto end;
×
1567
    }
1568

UNCOV
1569
    status = sixel_loader_setopt(loader,
×
1570
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1571
                                 cancel_flag);
1572
    if (SIXEL_FAILED(status)) {
×
UNCOV
1573
        goto end;
×
1574
    }
1575

UNCOV
1576
    status = sixel_loader_setopt(loader,
×
1577
                                 SIXEL_LOADER_OPTION_CONTEXT,
1578
                                 context);
UNCOV
1579
    if (SIXEL_FAILED(status)) {
×
UNCOV
1580
        goto end;
×
1581
    }
1582

UNCOV
1583
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1584

1585
end:
UNCOV
1586
    sixel_loader_unref(loader);
×
1587

UNCOV
1588
    return status;
×
1589
}
1590

1591

1592
SIXELAPI size_t
1593
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
152✔
1594
{
1595
    sixel_loader_entry_t const *entries;
96✔
1596
    sixel_loader_factory_t *factory;
96✔
1597
    size_t entry_count;
96✔
1598
    size_t limit;
96✔
1599
    size_t index;
96✔
1600
    SIXELSTATUS status;
96✔
1601

1602
    entries = NULL;
152✔
1603
    factory = NULL;
152✔
1604
    entry_count = 0u;
152✔
1605
    limit = 0u;
152✔
1606
    index = 0u;
152✔
1607
    status = SIXEL_FALSE;
152✔
1608

1609
    status = loader_factory_get_default(&factory);
152✔
1610
    if (SIXEL_FAILED(status)) {
152!
1611
        return 0u;
1612
    }
1613
    entry_count = loader_factory_get_entries(factory, &entries);
152✔
1614

1615
    if (names != NULL && max_names > 0) {
152!
1616
        limit = entry_count;
76✔
1617
        if (limit > max_names) {
76!
1618
            limit = max_names;
1619
        }
1620
        for (index = 0; index < limit; ++index) {
372!
1621
            names[index] = entries[index].name;
296✔
1622
        }
224✔
1623
    }
40✔
1624

1625
    loader_factory_unref(factory);
152✔
1626

1627
    return entry_count;
152✔
1628
}
80✔
1629

1630

1631
/* emacs Local Variables:      */
1632
/* emacs mode: c               */
1633
/* emacs tab-width: 4          */
1634
/* emacs indent-tabs-mode: nil */
1635
/* emacs c-basic-offset: 4     */
1636
/* emacs End:                  */
1637
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1638
/* 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