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

saitoha / libsixel / 22302695463

23 Feb 2026 10:40AM UTC coverage: 82.008% (+0.04%) from 81.969%
22302695463

push

github

saitoha
tests: add missing libjpeg loader coverage

27850 of 54973 branches covered (50.66%)

45791 of 55837 relevant lines covered (82.01%)

2565117.84 hits per line

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

85.42
/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-component-legacy.h"
111
#include "loader-wic.h"
112
#include "compat_stub.h"
113
#include "frame.h"
114
#include "chunk.h"
115
#include "allocator.h"
116
#include "encoder.h"
117
#include "logger.h"
118
#include "options.h"
119
#include "sixel_atomic.h"
120

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

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

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

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

168
int
169
sixel_loader_callback_is_canceled(void *data)
24,622✔
170
{
171
    sixel_loader_callback_state_t *state;
24,622✔
172

173
    state = (sixel_loader_callback_state_t *)data;
24,622✔
174
    if (state == NULL || state->loader == NULL ||
24,622!
175
        state->loader->cancel_flag == NULL) {
24,562✔
176
        return 0;
3,050✔
177
    }
178

179
    return *state->loader->cancel_flag != 0;
15,674✔
180
}
9,812✔
181

182

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

187
static char *
188
loader_strdup(char const *text, sixel_allocator_t *allocator)
1,226✔
189
{
190
    char *copy;
1,226✔
191
    size_t length;
1,226✔
192

193
    if (text == NULL) {
1,226!
194
        return NULL;
195
    }
196

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

203
    /* Copy the terminating NUL byte as part of length. */
204
    memcpy(copy, text, length);
1,226✔
205

206
    return copy;
1,226✔
207
}
622✔
208

209

210

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

229
    logger = NULL;
18,291✔
230
    if (loader != NULL) {
18,291!
231
        logger = &loader->logger;
18,291✔
232
    }
7,225✔
233
    if (logger == NULL || logger->file == NULL || !logger->active) {
18,291!
234
        return;
18,291✔
235
    }
236

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

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

290
static SIXELSTATUS
291
loader_callback_trampoline(sixel_frame_t *frame, void *data)
9,910✔
292
{
293
    sixel_loader_callback_state_t *state;
9,910✔
294
    SIXELSTATUS status;
9,910✔
295
    sixel_loader_t *loader;
9,910✔
296

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

303
    loader = state->loader;
9,910✔
304
    if (loader != NULL && loader->log_loader_finished == 0) {
9,910!
305
        loader_log_stage(loader,
12,622✔
306
                         "finish",
307
                         "path=%s loader=%s bytes=%zu",
308
                         loader->log_path,
9,055✔
309
                         loader->log_loader_name,
9,055✔
310
                         loader->log_input_bytes);
3,567✔
311
        loader->log_loader_finished = 1;
9,055✔
312
    }
3,567✔
313

314
    status = state->fn(frame, state->context);
9,910✔
315
    if (SIXEL_FAILED(status) && state->loader != NULL) {
9,910!
316
        state->loader->callback_failed = 1;
×
317
    }
318

319
    return status;
3,994✔
320
}
3,994✔
321

322

323
static SIXELSTATUS
324
loader_apply_component_options(sixel_loader_component_t *component,
9,294✔
325
                               sixel_loader_t const *loader,
326
                               int reqcolors)
327
{
328
    typedef struct loader_component_option_entry {
5,602✔
329
        int option;
330
        char const *name;
331
    } loader_component_option_entry_t;
332

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

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

368
    for (index = 0; index < sizeof(options) / sizeof(options[0]); ++index) {
65,058✔
369
        switch (options[index].option) {
55,764!
370
        case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
5,602✔
371
            value = &loader->fstatic;
9,294✔
372
            break;
9,294✔
373
        case SIXEL_LOADER_OPTION_USE_PALETTE:
5,602✔
374
            value = &loader->fuse_palette;
9,294✔
375
            break;
9,294✔
376
        case SIXEL_LOADER_OPTION_REQCOLORS:
377
            value = &reqcolors;
3,692✔
378
            break;
3,692✔
379
        case SIXEL_LOADER_OPTION_BGCOLOR:
5,602✔
380
            value = loader->has_bgcolor ? loader->bgcolor : NULL;
9,294✔
381
            break;
3,692✔
382
        case SIXEL_LOADER_OPTION_LOOP_CONTROL:
5,602✔
383
            value = &loader->loop_control;
9,294✔
384
            break;
9,294✔
385
        case SIXEL_LOADER_OPTION_START_FRAME_NO:
5,602✔
386
            value = loader->has_start_frame_no
5,978✔
387
                ? &loader->start_frame_no : NULL;
5,671✔
388
            break;
3,692✔
389
        default:
390
            value = NULL;
11,142✔
391
            break;
392
        }
393

394
        status = sixel_loader_component_setopt(component,
77,916✔
395
                                               options[index].option,
22,152✔
396
                                               value);
22,152✔
397
        if (SIXEL_FAILED(status)) {
55,764!
398
            (void)sixel_compat_snprintf(message,
×
399
                                        sizeof(message),
400
                                        "sixel_loader_load_file: "
401
                                        "failed to apply loader option "
402
                                        "'%s'.",
403
                                        options[index].name);
×
404
            sixel_helper_set_additional_message(
×
405
                message);
406
            return status;
×
407
        }
408
    }
22,152✔
409

410
    return SIXEL_OK;
3,692✔
411
}
3,692✔
412

413
static int
414
loader_plan_contains(sixel_loader_entry_t const **plan,
14,492✔
415
                     size_t plan_length,
416
                     sixel_loader_entry_t const *entry)
417
{
418
    size_t index;
14,492✔
419

420
    for (index = 0; index < plan_length; ++index) {
52,726!
421
        if (plan[index] == entry) {
29,740!
422
            return 1;
423
        }
424
    }
26,812✔
425

426
    return 0;
14,492✔
427
}
14,492✔
428

429

430
static int
431
loader_token_matches(char const *token,
432
                     size_t token_length,
433
                     char const *name);
434

435
#if HAVE_WIC
436
static sixel_suboption_key_t const g_subkeys_loader_wic_loader[] = {
437
    {
438
        "ico_minsize",
439
        NULL,
440
        "SIXEL_LODER_WIC_ICO_MINSIZE",
441
        SIXEL_SUBOPTION_VALUE_FREE,
442
        NULL,
443
        0u
444
    }
445
};
446
#endif
447

448
static sixel_option_value_schema_t const g_schema_loader_values_loader[] = {
449
#if HAVE_LIBPNG
450
    { "libpng", 0, NULL, 0u },
451
#endif
452
#if HAVE_JPEG
453
    { "libjpeg", 0, NULL, 0u },
454
#endif
455
#if HAVE_WEBP
456
    { "libwebp", 0, NULL, 0u },
457
#endif
458
#if HAVE_LIBTIFF
459
    { "libtiff", 0, NULL, 0u },
460
#endif
461
    { "builtin", 0, NULL, 0u },
462
#if HAVE_WIC
463
    {
464
        "wic",
465
        0,
466
        g_subkeys_loader_wic_loader,
467
        sizeof(g_subkeys_loader_wic_loader)
468
            / sizeof(g_subkeys_loader_wic_loader[0])
469
    },
470
#endif
471
#if HAVE_COREGRAPHICS
472
    { "coregraphics", 0, NULL, 0u },
473
#endif
474
#ifdef HAVE_GDK_PIXBUF2
475
    { "gdk-pixbuf2", 0, NULL, 0u },
476
#endif
477
#if HAVE_GD
478
    { "gd", 0, NULL, 0u },
479
#endif
480
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
481
    { "quicklook", 0, NULL, 0u },
482
#endif
483
#if HAVE_FREEDESKTOP_THUMBNAILING
484
    { "gnome-thumbnailer", 0, NULL, 0u },
485
#endif
486
};
487

488
static sixel_option_argument_schema_t const g_schema_loaders_loader = {
489
    SIXEL_OPTFLAG_LOADERS,
490
    "--loaders",
491
    g_schema_loader_values_loader,
492
    sizeof(g_schema_loader_values_loader)
493
        / sizeof(g_schema_loader_values_loader[0])
494
};
495

496
static size_t
497
loader_token_name_length(char const *token, size_t token_length)
626✔
498
{
499
    size_t index;
626✔
500

501
    index = 0;
626✔
502
    while (index < token_length) {
10,727!
503
        if (token[index] == ':') {
9,502!
504
            break;
505
        }
506
        ++index;
9,494✔
507
    }
508

509
    return index;
1,859✔
510
}
626✔
511

512
static int
513
loader_parse_positive_int(char const *text, size_t length, int *value_out)
8✔
514
{
515
    size_t index;
8✔
516
    int value;
8✔
517
    unsigned char digit;
8✔
518

519
    if (text == NULL || value_out == NULL || length == 0) {
8!
520
        return 0;
521
    }
522

523
    value = 0;
524
    index = 0;
525
    while (index < length) {
24!
526
        digit = (unsigned char)text[index];
16✔
527
        if (digit < (unsigned char)'0' || digit > (unsigned char)'9') {
16!
528
            return 0;
529
        }
530
        if (value > (INT_MAX - 9) / 10) {
16!
531
            return 0;
532
        }
533
        value = value * 10 + (digit - (unsigned char)'0');
16✔
534
        ++index;
16✔
535
    }
536

537
    if (value <= 0) {
8!
538
        return 0;
539
    }
540

541
    *value_out = value;
8✔
542
    return 1;
8✔
543
}
544

545
static void
546
loader_apply_loader_suboptions(char const *order)
9,208✔
547
{
548
    char const *cursor;
9,208✔
549
    char const *token_start;
9,208✔
550
    char const *token_end;
9,208✔
551
    char const *order_end;
9,208✔
552
    size_t token_length;
9,208✔
553
    char token_buffer[256];
9,208✔
554
    char match_detail[128];
9,208✔
555
    sixel_option_argument_resolution_t resolution;
9,208✔
556
    size_t assignment_index;
9,208✔
557
    int parsed_value;
9,208✔
558

559
    cursor = order;
9,208✔
560
    token_start = order;
9,208✔
561
    token_end = order;
9,208✔
562
    order_end = NULL;
9,208✔
563
    token_length = 0;
9,208✔
564
    token_buffer[0] = '\0';
9,208✔
565
    match_detail[0] = '\0';
9,208✔
566
    resolution.resolved_base_value = 0;
9,208✔
567
    resolution.base_def = NULL;
9,208✔
568
    resolution.assignments = NULL;
9,208✔
569
    resolution.assignment_count = 0u;
9,208✔
570
    assignment_index = 0u;
9,208✔
571
    parsed_value = 0;
9,208✔
572

573
    sixel_helper_set_wic_ico_minsize(0);
9,208✔
574

575
    if (order == NULL || order[0] == '\0') {
9,208!
576
        return;
7,975✔
577
    }
578

579
    order_end = order + strlen(order);
1,233✔
580
    while (order_end > order && isspace((unsigned char)order_end[-1])) {
1,233!
581
        --order_end;
×
582
    }
583
    if (order_end > order && order_end[-1] == '!') {
1,233!
584
        --order_end;
1,233✔
585
    }
626✔
586

587
    token_start = order;
1,233✔
588
    cursor = order;
1,233✔
589
    while (cursor <= order_end) {
12,080✔
590
        if (cursor == order_end || *cursor == ',') {
10,847!
591
            token_end = cursor;
1,233✔
592
            while (token_start < token_end &&
1,233!
593
                   isspace((unsigned char)*token_start)) {
1,233!
594
                ++token_start;
×
595
            }
596
            while (token_end > token_start &&
1,233!
597
                   isspace((unsigned char)token_end[-1])) {
1,233!
598
                --token_end;
×
599
            }
600
            token_length = (size_t)(token_end - token_start);
1,233✔
601
            if (token_length > 0 && token_length < sizeof(token_buffer)) {
1,233!
602
                memcpy(token_buffer, token_start, token_length);
1,233✔
603
                token_buffer[token_length] = '\0';
1,233✔
604
                if (SIXEL_SUCCEEDED(sixel_option_parse_argument_with_suboptions(
1,859!
605
                        token_buffer,
606
                        &g_schema_loaders_loader,
607
                        &resolution,
608
                        match_detail,
609
                        sizeof(match_detail))) &&
1,233!
610
                    resolution.base_def != NULL &&
1,233!
611
                    strcmp(resolution.base_def->name, "wic") == 0) {
1,233!
612
                    assignment_index = 0u;
613
                    while (assignment_index < resolution.assignment_count) {
156!
614
                        if (strcmp(resolution.assignments[assignment_index]
8!
615
                                   .resolved_key_name,
616
                                   "ico_minsize") == 0 &&
8!
617
                            loader_parse_positive_int(
8✔
618
                                resolution.assignments[assignment_index]
619
                                .resolved_value_text,
620
                                strlen(resolution.assignments[assignment_index]
621
                                       .resolved_value_text),
8✔
622
                                &parsed_value)) {
623
                            sixel_helper_set_wic_ico_minsize(parsed_value);
8✔
624
                        }
625
                        ++assignment_index;
8✔
626
                    }
627
                }
628
                sixel_option_free_argument_resolution(&resolution);
1,233✔
629
            }
626✔
630
            token_start = cursor + 1;
1,233✔
631
        }
626✔
632
        ++cursor;
10,847✔
633
    }
634
}
3,642!
635

636
static int
637
loader_token_matches(char const *token,
2,541✔
638
                     size_t token_length,
639
                     char const *name)
640
{
641
    size_t index;
2,541✔
642
    unsigned char left;
2,541✔
643
    unsigned char right;
2,541✔
644

645
    for (index = 0; index < token_length && name[index] != '\0'; ++index) {
12,294!
646
        left = (unsigned char)token[index];
11,061✔
647
        right = (unsigned char)name[index];
11,061✔
648
        if (tolower(left) != tolower(right)) {
11,061✔
649
            return 0;
1,100✔
650
        }
651
    }
5,496✔
652

653
    if (index != token_length || name[index] != '\0') {
1,233!
654
        return 0;
×
655
    }
656

657
    return 1;
626✔
658
}
1,726✔
659

660
static sixel_loader_entry_t const *
661
loader_lookup_token(char const *token,
1,233✔
662
                    size_t token_length,
663
                    sixel_loader_entry_t const *entries,
664
                    size_t entry_count)
665
{
666
    size_t index;
1,233✔
667

668
    for (index = 0; index < entry_count; ++index) {
2,541!
669
        if (loader_token_matches(token,
4,267✔
670
                                 token_length,
1,726✔
671
                                 entries[index].name)) {
2,541✔
672
            return &entries[index];
626✔
673
        }
674
    }
1,100✔
675

676
    return NULL;
677
}
626✔
678

679
/*
680
 * loader_build_plan
681
 *
682
 * Translate a comma separated list into an execution plan that reorders the
683
 * runtime loader chain.  Tokens are matched case-insensitively.  Unknown names
684
 * are ignored so that new builds remain forward compatible.
685
 *
686
 * When the input ends with "!", the default fallback list is suppressed so
687
 * only the explicit tokens are tried.
688
 *
689
 *    user input "gd,coregraphics"
690
 *                |
691
 *                v
692
 *        +-------------------+
693
 *        | prioritized list  |
694
 *        +-------------------+
695
 *                |
696
 *                v
697
 *        +-------------------+
698
 *        | default fallbacks |
699
 *        +-------------------+
700
 */
701
static size_t
702
loader_build_plan(char const *order,
9,208✔
703
                  sixel_loader_entry_t const *entries,
704
                  size_t entry_count,
705
                  sixel_loader_entry_t const **plan,
706
                  size_t plan_capacity)
707
{
708
    size_t plan_length;
9,208✔
709
    size_t index;
9,208✔
710
    char const *cursor;
9,208✔
711
    char const *token_start;
9,208✔
712
    char const *token_end;
9,208✔
713
    char const *order_end;
9,208✔
714
    size_t token_length;
9,208✔
715
    sixel_loader_entry_t const *entry;
9,208✔
716
    size_t limit;
9,208✔
717
    int allow_fallback;
9,208✔
718

719
    plan_length = 0;
9,208✔
720
    index = 0;
9,208✔
721
    cursor = order;
9,208✔
722
    token_start = order;
9,208✔
723
    token_end = order;
9,208✔
724
    order_end = NULL;
9,208✔
725
    token_length = 0;
9,208✔
726
    entry = NULL;
9,208✔
727
    limit = plan_capacity;
9,208✔
728
    allow_fallback = 1;
9,208✔
729

730
    if (order != NULL) {
9,208✔
731
        order_end = order + strlen(order);
1,233✔
732
        while (order_end > order &&
1,233!
733
               isspace((unsigned char)order_end[-1])) {
1,233!
734
            --order_end;
×
735
        }
736
        if (order_end > order && order_end[-1] == '!') {
1,233!
737
            allow_fallback = 0;
1,233✔
738
            --order_end;
1,233✔
739
            while (order_end > order &&
1,233!
740
                   isspace((unsigned char)order_end[-1])) {
1,233!
741
                --order_end;
×
742
            }
743
        }
626✔
744
    }
626✔
745

746
    if (order != NULL && plan != NULL && plan_capacity > 0) {
9,208!
747
        token_start = order;
626✔
748
        cursor = order;
626✔
749
        while (cursor < order_end) {
10,847✔
750
            if (*cursor == ',') {
9,614!
751
                token_end = cursor;
×
752
                while (token_start < token_end &&
×
753
                       isspace((unsigned char)*token_start)) {
×
754
                    ++token_start;
×
755
                }
756
                while (token_end > token_start &&
×
757
                       isspace((unsigned char)token_end[-1])) {
×
758
                    --token_end;
×
759
                }
760
                token_length = (size_t)(token_end - token_start);
×
761
                if (token_length > 0) {
×
762
                    entry = loader_lookup_token(token_start,
×
763
                                                loader_token_name_length(
764
                                                    token_start,
765
                                                    token_length),
766
                                                entries,
767
                                                entry_count);
768
                    if (entry != NULL &&
×
769
                        !loader_plan_contains(plan,
×
770
                                              plan_length,
771
                                              entry) &&
×
772
                        plan_length < limit) {
773
                        plan[plan_length] = entry;
×
774
                        ++plan_length;
×
775
                    }
776
                }
777
                token_start = cursor + 1;
×
778
            }
779
            ++cursor;
9,614✔
780
        }
781

782
        token_end = order_end;
1,233✔
783
        while (token_start < token_end &&
1,233!
784
               isspace((unsigned char)*token_start)) {
1,233!
785
            ++token_start;
×
786
        }
787
        while (token_end > token_start &&
1,233!
788
               isspace((unsigned char)token_end[-1])) {
1,233!
789
            --token_end;
×
790
        }
791
        token_length = (size_t)(token_end - token_start);
1,233✔
792
        if (token_length > 0) {
1,233!
793
            entry = loader_lookup_token(token_start,
1,859✔
794
                                        loader_token_name_length(
626✔
795
                                            token_start,
626✔
796
                                            token_length),
626✔
797
                                        entries,
626✔
798
                                        entry_count);
626✔
799
            if (entry != NULL &&
1,859!
800
                !loader_plan_contains(plan, plan_length, entry) &&
1,233!
801
                plan_length < limit) {
626✔
802
                plan[plan_length] = entry;
1,233✔
803
                ++plan_length;
1,233✔
804
            }
626✔
805
        }
626✔
806
    }
626✔
807

808
    if (allow_fallback) {
9,208✔
809
        for (index = 0; index < entry_count && plan_length < limit; ++index) {
34,775✔
810
            entry = &entries[index];
26,800✔
811
            if (!entry->default_enabled) {
26,800✔
812
                continue;
5,047✔
813
            }
814
            if (!loader_plan_contains(plan, plan_length, entry)) {
21,753!
815
                plan[plan_length] = entry;
21,753✔
816
                ++plan_length;
21,753✔
817
            }
13,866✔
818
        }
13,866✔
819
    }
3,016✔
820

821
    return plan_length;
12,850✔
822
}
3,642✔
823

824
static void
825
loader_append_chunk(char *dest,
164✔
826
                    size_t capacity,
827
                    size_t *offset,
828
                    char const *chunk)
829
{
830
    size_t available;
164✔
831
    size_t length;
164✔
832

833
    if (dest == NULL || offset == NULL || chunk == NULL) {
164!
834
        return;
835
    }
836

837
    if (*offset >= capacity) {
164!
838
        return;
839
    }
840

841
    available = capacity - *offset;
164✔
842
    if (available == 0) {
164!
843
        return;
844
    }
845

846
    length = strlen(chunk);
164✔
847
    if (length >= available) {
164!
848
        if (available == 0) {
×
849
            return;
850
        }
851
        length = available - 1u;
×
852
    }
853

854
    if (length > 0) {
164!
855
        memcpy(dest + *offset, chunk, length);
164✔
856
        *offset += length;
164✔
857
    }
164✔
858

859
    if (*offset < capacity) {
164!
860
        dest[*offset] = '\0';
164✔
861
    } else {
164✔
862
        dest[capacity - 1u] = '\0';
×
863
    }
864
}
164!
865

866
static void
867
loader_append_key_value(char *dest,
76✔
868
                        size_t capacity,
869
                        size_t *offset,
870
                        char const *label,
871
                        char const *value)
872
{
873
    char line[128];
76✔
874
    int written;
76✔
875

876
    if (value == NULL || value[0] == '\0') {
76!
877
        return;
×
878
    }
879

880
    written = sixel_compat_snprintf(line,
152✔
881
                                    sizeof(line),
882
                                    "  %-10s: %s\n",
883
                                    label,
76✔
884
                                    value);
76✔
885
    if (written < 0) {
76!
886
        return;
887
    }
888

889
    if ((size_t)written >= sizeof(line)) {
76!
890
        line[sizeof(line) - 1u] = '\0';
×
891
    }
892

893
    loader_append_chunk(dest, capacity, offset, line);
76✔
894
}
76!
895

896
static void
897
loader_extract_extension(char const *path, char *buffer, size_t capacity)
22✔
898
{
899
    char const *dot;
22✔
900
    size_t index;
22✔
901

902
    if (buffer == NULL || capacity == 0) {
22!
903
        return;
904
    }
905

906
    buffer[0] = '\0';
22✔
907

908
    if (path == NULL) {
22!
909
        return;
910
    }
911

912
    dot = strrchr(path, '.');
22✔
913
    if (dot == NULL || dot[1] == '\0') {
22!
914
        return;
10✔
915
    }
916

917
#if defined(_WIN32)
918
    {
919
        char const *slash;
920
        char const *backslash;
921

922
        slash = strrchr(path, '/');
923
        backslash = strrchr(path, '\\');
924
        if ((slash != NULL && dot < slash) ||
925
                (backslash != NULL && dot < backslash)) {
926
            return;
927
        }
928
    }
929
#else
930
    {
931
        char const *slash;
12✔
932

933
        slash = strrchr(path, '/');
12✔
934
        if (slash != NULL && dot < slash) {
12!
935
            return;
936
        }
937
    }
12!
938
#endif
939

940
    if (dot[1] == '\0') {
12!
941
        return;
942
    }
943

944
    dot += 1;
12✔
945

946
    for (index = 0; index + 1 < capacity && dot[index] != '\0'; ++index) {
48!
947
        buffer[index] = (char)tolower((unsigned char)dot[index]);
36✔
948
    }
36✔
949
    buffer[index] = '\0';
12✔
950
}
22!
951

952

953

954

955

956

957

958

959

960

961

962
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
963
static void
964
loader_copy_cfstring(CFStringRef source, char *buffer, size_t capacity)
965
{
966
    if (buffer == NULL || capacity == 0) {
967
        return;
968
    }
969

970
    buffer[0] = '\0';
971
    if (source == NULL) {
972
        return;
973
    }
974

975
    if (!CFStringGetCString(source,
976
                             buffer,
977
                             (CFIndex)capacity,
978
                             kCFStringEncodingUTF8)) {
979
        buffer[0] = '\0';
980
    }
981
}
982
#endif
983

984

985
static void
986
loader_publish_diagnostic(sixel_chunk_t const *pchunk,
22✔
987
                          char const *filename)
988
{
989
    enum { description_length = 128 };
990
    enum { uttype_length = 128 };
991
    enum { extension_length = 32 };
992
    enum { message_length = 768 };
993
    char message[message_length];
22✔
994
    char type_value[description_length];
22✔
995
    char extension_text[extension_length + 2];
22✔
996
    char uttype[uttype_length];
22✔
997
    char desc_buffer[description_length];
22✔
998
    char extension[extension_length];
22✔
999
    char const *path;
22✔
1000
    char const *display_path;
22✔
1001
    char const *metadata_path;
22✔
1002
    char const *description_text;
22✔
1003
    char *mime_string;
22✔
1004
    char *description_string;
22✔
1005
    size_t offset;
22✔
1006
    int gnome_available;
22✔
1007
    int gnome_has_dirs;
22✔
1008
    int gnome_has_match;
22✔
1009
    int suggestions;
22✔
1010

1011
    message[0] = '\0';
22✔
1012
    type_value[0] = '\0';
22✔
1013
    extension_text[0] = '\0';
22✔
1014
    uttype[0] = '\0';
22✔
1015
    desc_buffer[0] = '\0';
22✔
1016
    extension[0] = '\0';
22✔
1017
    path = NULL;
22✔
1018
    display_path = "(stdin)";
22✔
1019
    metadata_path = NULL;
22✔
1020
    description_text = NULL;
22✔
1021
    mime_string = NULL;
22✔
1022
    description_string = NULL;
22✔
1023
    offset = 0u;
22✔
1024
    gnome_available = 0;
22✔
1025
    gnome_has_dirs = 0;
22✔
1026
    gnome_has_match = 0;
22✔
1027
    suggestions = 0;
22✔
1028

1029
    if (pchunk != NULL && pchunk->source_path != NULL) {
22!
1030
        path = pchunk->source_path;
20✔
1031
    } else if (filename != NULL) {
22!
1032
        path = filename;
2✔
1033
    }
2✔
1034

1035
    if (path != NULL && strcmp(path, "-") != 0) {
22!
1036
        display_path = path;
20✔
1037
    }
20✔
1038

1039
    if (path != NULL && strcmp(path, "-") != 0 &&
22!
1040
            strstr(path, "://") == NULL) {
20!
1041
        metadata_path = path;
20✔
1042
    }
20✔
1043

1044
    loader_extract_extension(path, extension, sizeof(extension));
22✔
1045

1046
#if HAVE_FREEDESKTOP_THUMBNAILING
1047
    if (metadata_path != NULL) {
22!
1048
        /*
1049
         * Collect MIME metadata via file(1) when fork() and friends are
1050
         * available.  Windows builds compiled with clang64 lack these
1051
         * interfaces, so the thumbnail helpers remain disabled there.
1052
         */
1053
        mime_string = thumbnailer_guess_content_type(metadata_path);
20✔
1054
        description_string = thumbnailer_run_file(metadata_path, NULL);
20✔
1055
    }
20✔
1056
#else
1057
    (void)metadata_path;
1058
#endif
1059

1060
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1061
#if defined(__clang__)
1062
    /*
1063
     * Allow use of legacy UTType C APIs when compiling with the
1064
     * macOS 12 SDK.  The replacement interfaces are Objective-C only,
1065
     * so we must intentionally silence the deprecation warnings here.
1066
     */
1067
#pragma clang diagnostic push
1068
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
1069
#endif
1070
    {
1071
        CFStringRef uti_ref;
1072
        CFStringRef mime_ref;
1073
        CFStringRef ext_ref;
1074
        CFStringRef desc_ref;
1075
        CFStringRef preferred_mime;
1076
        char uti_local[uttype_length];
1077
        char desc_local[description_length];
1078
        char mime_local[64];
1079

1080
        uti_ref = NULL;
1081
        mime_ref = NULL;
1082
        ext_ref = NULL;
1083
        desc_ref = NULL;
1084
        preferred_mime = NULL;
1085
        uti_local[0] = '\0';
1086
        desc_local[0] = '\0';
1087
        mime_local[0] = '\0';
1088

1089
        if (mime_string != NULL) {
1090
            mime_ref = CFStringCreateWithCString(kCFAllocatorDefault,
1091
                                                 mime_string,
1092
                                                 kCFStringEncodingUTF8);
1093
        }
1094
        if (mime_ref != NULL) {
1095
            uti_ref = UTTypeCreatePreferredIdentifierForTag(
1096
                kUTTagClassMIMEType,
1097
                mime_ref,
1098
                NULL);
1099
        }
1100
        if (uti_ref == NULL && extension[0] != '\0') {
1101
            ext_ref = CFStringCreateWithCString(kCFAllocatorDefault,
1102
                                                extension,
1103
                                                kCFStringEncodingUTF8);
1104
            if (ext_ref != NULL) {
1105
                uti_ref = UTTypeCreatePreferredIdentifierForTag(
1106
                    kUTTagClassFilenameExtension,
1107
                    ext_ref,
1108
                    NULL);
1109
            }
1110
        }
1111
        if (uti_ref != NULL) {
1112
            loader_copy_cfstring(uti_ref, uti_local, sizeof(uti_local));
1113
            desc_ref = UTTypeCopyDescription(uti_ref);
1114
            if (desc_ref != NULL) {
1115
                loader_copy_cfstring(desc_ref,
1116
                                     desc_local,
1117
                                     sizeof(desc_local));
1118
                CFRelease(desc_ref);
1119
                desc_ref = NULL;
1120
            }
1121
            if (mime_string == NULL) {
1122
                preferred_mime = UTTypeCopyPreferredTagWithClass(
1123
                    uti_ref,
1124
                    kUTTagClassMIMEType);
1125
                if (preferred_mime != NULL) {
1126
                    loader_copy_cfstring(preferred_mime,
1127
                                         mime_local,
1128
                                         sizeof(mime_local));
1129
                    CFRelease(preferred_mime);
1130
                    preferred_mime = NULL;
1131
                }
1132
                if (mime_local[0] != '\0') {
1133
                    mime_string = thumbnailer_strdup(mime_local);
1134
                }
1135
            }
1136
        }
1137
        if (mime_ref != NULL) {
1138
            CFRelease(mime_ref);
1139
        }
1140
        if (ext_ref != NULL) {
1141
            CFRelease(ext_ref);
1142
        }
1143
        if (uti_ref != NULL) {
1144
            CFRelease(uti_ref);
1145
        }
1146
        if (uti_local[0] != '\0') {
1147
            sixel_compat_snprintf(uttype,
1148
                                  sizeof(uttype),
1149
                                  "%s",
1150
                                  uti_local);
1151
        }
1152
        if (desc_local[0] != '\0') {
1153
            sixel_compat_snprintf(desc_buffer,
1154
                                  sizeof(desc_buffer),
1155
                                  "%s",
1156
                                  desc_local);
1157
        }
1158
    }
1159
#if defined(__clang__)
1160
#pragma clang diagnostic pop
1161
#endif
1162
#endif
1163

1164
    if (description_string != NULL && description_string[0] != '\0') {
22!
1165
        description_text = description_string;
20✔
1166
    } else if (desc_buffer[0] != '\0') {
22!
1167
        description_text = desc_buffer;
1168
    } else {
1169
        description_text = "unknown content";
2✔
1170
    }
1171

1172
    sixel_compat_snprintf(type_value,
44✔
1173
                          sizeof(type_value),
1174
                          "%s",
1175
                          description_text);
22✔
1176

1177
    loader_append_chunk(message,
22✔
1178
                        sizeof(message),
1179
                        &offset,
1180
                        "diagnostic:\n");
1181
    loader_append_key_value(message,
44✔
1182
                            sizeof(message),
1183
                            &offset,
1184
                            "file",
1185
                            display_path);
22✔
1186
    loader_append_key_value(message,
44✔
1187
                            sizeof(message),
1188
                            &offset,
1189
                            "type",
1190
                            type_value);
22✔
1191

1192
    if (mime_string != NULL && mime_string[0] != '\0') {
22!
1193
        loader_append_key_value(message,
40✔
1194
                                sizeof(message),
1195
                                &offset,
1196
                                "mime",
1197
                                mime_string);
20✔
1198
    }
20✔
1199

1200
    if (uttype[0] != '\0') {
22!
1201
        loader_append_key_value(message,
×
1202
                                sizeof(message),
1203
                                &offset,
1204
                                "uti",
1205
                                uttype);
1206
    }
1207

1208
    if (extension[0] != '\0') {
22!
1209
        sixel_compat_snprintf(extension_text,
24✔
1210
                              sizeof(extension_text),
1211
                              ".%s",
1212
                              extension);
12✔
1213
        loader_append_key_value(message,
24✔
1214
                                sizeof(message),
1215
                                &offset,
1216
                                "extension",
1217
                                extension_text);
12✔
1218
    }
12✔
1219

1220
    loader_append_chunk(message,
22✔
1221
                        sizeof(message),
1222
                        &offset,
1223
                        "  suggestions:\n");
1224

1225
#if HAVE_COREGRAPHICS && HAVE_QUICKLOOK
1226
    int quicklook_available;
1227
    int quicklook_supported;
1228

1229
    quicklook_available = 0;
1230
    quicklook_supported = 0;
1231

1232
    quicklook_available = loader_registry_entry_available("quicklook");
1233
    if (quicklook_available) {
1234
        quicklook_supported = loader_quicklook_can_decode(pchunk, filename);
1235
    }
1236
    if (quicklook_supported) {
1237
        loader_append_chunk(message,
1238
                            sizeof(message),
1239
                            &offset,
1240
                            "    - QuickLook rendered a preview during "
1241
                            "the probe; try -L quicklook.\n");
1242
        suggestions += 1;
1243
    }
1244
#endif
1245

1246
#if HAVE_FREEDESKTOP_THUMBNAILING
1247
    gnome_available = loader_registry_entry_available("gnome-thumbnailer");
22✔
1248
    if (gnome_available) {
22!
1249
        loader_probe_gnome_thumbnailers(mime_string,
22✔
1250
                                        &gnome_has_dirs,
1251
                                        &gnome_has_match);
1252
        if (gnome_has_dirs && gnome_has_match) {
22!
1253
            loader_append_chunk(message,
1254
                                sizeof(message),
1255
                                &offset,
1256
                                "    - GNOME thumbnailer definitions match "
1257
                                "this MIME type; try -L gnome-thumbnailer.\n"
1258
                                );
1259
            suggestions += 1;
1260
        }
1261
    }
22✔
1262
#else
1263
    (void)gnome_available;
1264
    (void)gnome_has_dirs;
1265
    (void)gnome_has_match;
1266
#endif
1267

1268
    if (suggestions == 0) {
22!
1269
        loader_append_chunk(message,
22✔
1270
                            sizeof(message),
1271
                            &offset,
1272
                            "    (no thumbnail helper hints)\n");
1273
    }
22✔
1274

1275
    if (suggestions > 0) {
22!
1276
        loader_append_chunk(message,
1277
                            sizeof(message),
1278
                            &offset,
1279
                            "  hint       : Enable one of the suggested "
1280
                            "loaders with -L.\n");
1281
    } else {
1282
        loader_append_chunk(message,
22✔
1283
                            sizeof(message),
1284
                            &offset,
1285
                            "  hint       : Convert the file to PNG or "
1286
                            "enable optional loaders.\n");
1287
    }
1288

1289
    sixel_helper_set_additional_message(message);
22✔
1290

1291
    free(mime_string);
22✔
1292
    free(description_string);
22✔
1293
}
22✔
1294

1295
SIXELAPI SIXELSTATUS
1296
sixel_loader_new(
9,236✔
1297
    sixel_loader_t   /* out */ **pploader,
1298
    sixel_allocator_t/* in */  *allocator)
1299
{
1300
    SIXELSTATUS status = SIXEL_FALSE;
9,236✔
1301
    sixel_loader_t *loader;
9,236✔
1302
    sixel_allocator_t *local_allocator;
9,236✔
1303

1304
    loader = NULL;
9,236✔
1305
    local_allocator = allocator;
9,236✔
1306

1307
    if (pploader == NULL) {
9,236!
1308
        sixel_helper_set_additional_message(
×
1309
            "sixel_loader_new: pploader is null.");
1310
        status = SIXEL_BAD_ARGUMENT;
×
1311
        goto end;
×
1312
    }
1313

1314
    if (local_allocator == NULL) {
9,236!
1315
        status = sixel_allocator_new(&local_allocator,
×
1316
                                     NULL,
1317
                                     NULL,
1318
                                     NULL,
1319
                                     NULL);
1320
        if (SIXEL_FAILED(status)) {
×
1321
            goto end;
×
1322
        }
1323
    } else {
1324
        sixel_allocator_ref(local_allocator);
9,236✔
1325
    }
1326

1327
    loader = (sixel_loader_t *)sixel_allocator_malloc(local_allocator,
9,236✔
1328
                                                      sizeof(*loader));
1329
    if (loader == NULL) {
9,236!
1330
        sixel_helper_set_additional_message(
×
1331
            "sixel_loader_new: sixel_allocator_malloc() failed.");
1332
        status = SIXEL_BAD_ALLOCATION;
×
1333
        sixel_allocator_unref(local_allocator);
×
1334
        goto end;
×
1335
    }
1336

1337
    loader->ref = 1U;
9,236✔
1338
    loader->fstatic = 0;
9,236✔
1339
    loader->fuse_palette = 0;
9,236✔
1340
    loader->reqcolors = SIXEL_PALETTE_MAX;
9,236✔
1341
    loader->bgcolor[0] = 0;
9,236✔
1342
    loader->bgcolor[1] = 0;
9,236✔
1343
    loader->bgcolor[2] = 0;
9,236✔
1344
    loader->has_bgcolor = 0;
9,236✔
1345
    loader->loop_control = SIXEL_LOOP_AUTO;
9,236✔
1346
    loader->finsecure = 0;
9,236✔
1347
    loader->has_start_frame_no = 0;
9,236✔
1348
    loader->start_frame_no = INT_MIN;
9,236✔
1349
    loader->cancel_flag = NULL;
9,236✔
1350
    loader->context = NULL;
9,236✔
1351
    /*
1352
     * Initialize a private logger. The helper reuses an existing global
1353
     * logger sink when present so loader markers share the timeline with
1354
     * upstream stages without requiring sixel_loader_setopt().
1355
     */
1356
    sixel_logger_init(&loader->logger);
9,236✔
1357
    (void)sixel_logger_prepare_env(&loader->logger);
9,236✔
1358
    loader->loader_order = NULL;
9,236✔
1359
    loader->allocator = local_allocator;
9,236✔
1360
    loader->last_loader_name[0] = '\0';
9,236✔
1361
    loader->last_source_path[0] = '\0';
9,236✔
1362
    loader->last_input_bytes = 0u;
9,236✔
1363
    loader->callback_failed = 0;
9,236✔
1364
    loader->log_loader_finished = 0;
9,236✔
1365
    loader->log_path[0] = '\0';
9,236✔
1366
    loader->log_loader_name[0] = '\0';
9,236✔
1367
    loader->log_input_bytes = 0u;
9,236✔
1368

1369
    *pploader = loader;
9,236✔
1370
    status = SIXEL_OK;
9,236✔
1371

1372
end:
5,578✔
1373
    return status;
12,894✔
1374
}
3,658✔
1375

1376
SIXELAPI void
1377
sixel_loader_ref(
83,620✔
1378
    sixel_loader_t /* in */ *loader)
1379
{
1380
    if (loader == NULL) {
83,620!
1381
        return;
1382
    }
1383

1384
    (void)sixel_atomic_fetch_add_u32(&loader->ref, 1U);
83,620✔
1385
}
33,262✔
1386

1387
SIXELAPI void
1388
sixel_loader_unref(
92,856✔
1389
    sixel_loader_t /* in */ *loader)
1390
{
1391
    sixel_allocator_t *allocator;
92,856✔
1392
    unsigned int previous;
92,856✔
1393

1394
    if (loader == NULL) {
92,856!
1395
        return;
1396
    }
1397

1398
    previous = sixel_atomic_fetch_sub_u32(&loader->ref, 1U);
92,856✔
1399
    if (previous == 1U) {
92,856✔
1400
        allocator = loader->allocator;
9,236✔
1401
        sixel_logger_close(&loader->logger);
9,236✔
1402
        sixel_allocator_free(allocator, loader->loader_order);
9,236✔
1403
        sixel_allocator_free(allocator, loader);
9,236✔
1404
        sixel_allocator_unref(allocator);
9,236✔
1405
    }
3,658✔
1406
}
36,920!
1407

1408
SIXELAPI SIXELSTATUS
1409
sixel_loader_setopt(
74,384✔
1410
    sixel_loader_t /* in */ *loader,
1411
    int            /* in */ option,
1412
    void const     /* in */ *value)
1413
{
1414
    SIXELSTATUS status = SIXEL_FALSE;
74,384✔
1415
    int const *flag;
74,384✔
1416
    unsigned char const *color;
74,384✔
1417
    char const *order;
74,384✔
1418
    char *copy;
74,384✔
1419
    sixel_allocator_t *allocator;
74,384✔
1420

1421
    flag = NULL;
74,384✔
1422
    color = NULL;
74,384✔
1423
    order = NULL;
74,384✔
1424
    copy = NULL;
74,384✔
1425
    allocator = NULL;
74,384✔
1426

1427
    if (loader == NULL) {
74,384!
1428
        sixel_helper_set_additional_message(
×
1429
            "sixel_loader_setopt: loader is null.");
1430
        status = SIXEL_BAD_ARGUMENT;
×
1431
        goto end0;
×
1432
    }
1433

1434
    sixel_loader_ref(loader);
74,384✔
1435

1436
    switch (option) {
74,384!
1437
    case SIXEL_LOADER_OPTION_REQUIRE_STATIC:
5,578✔
1438
        flag = (int const *)value;
9,236✔
1439
        loader->fstatic = flag != NULL ? *flag : 0;
9,236!
1440
        status = SIXEL_OK;
9,236✔
1441
        break;
9,236✔
1442
    case SIXEL_LOADER_OPTION_USE_PALETTE:
5,578✔
1443
        flag = (int const *)value;
9,236✔
1444
        loader->fuse_palette = flag != NULL ? *flag : 0;
9,236!
1445
        status = SIXEL_OK;
9,236✔
1446
        break;
9,236✔
1447
    case SIXEL_LOADER_OPTION_REQCOLORS:
5,578✔
1448
        flag = (int const *)value;
9,236✔
1449
        loader->reqcolors = flag != NULL ? *flag : SIXEL_PALETTE_MAX;
9,236!
1450
        if (loader->reqcolors < 1) {
9,236!
1451
            sixel_helper_set_additional_message(
×
1452
                "sixel_loader_setopt: reqcolors must be 1 or greater.");
1453
            status = SIXEL_BAD_ARGUMENT;
×
1454
            goto end;
×
1455
        }
1456
        if (loader->reqcolors > SIXEL_PALETTE_MAX) {
9,236!
1457
            loader->reqcolors = SIXEL_PALETTE_MAX;
×
1458
        }
1459
        status = SIXEL_OK;
3,658✔
1460
        break;
3,658✔
1461
    case SIXEL_LOADER_OPTION_BGCOLOR:
2,828✔
1462
        if (value == NULL) {
4,742✔
1463
            loader->has_bgcolor = 0;
4,653✔
1464
        } else {
1,860✔
1465
            color = (unsigned char const *)value;
89✔
1466
            loader->bgcolor[0] = color[0];
89✔
1467
            loader->bgcolor[1] = color[1];
89✔
1468
            loader->bgcolor[2] = color[2];
89✔
1469
            loader->has_bgcolor = 1;
89✔
1470
        }
1471
        status = SIXEL_OK;
1,914✔
1472
        break;
1,914✔
1473
    case SIXEL_LOADER_OPTION_LOOP_CONTROL:
5,578✔
1474
        flag = (int const *)value;
9,236✔
1475
        loader->loop_control = flag != NULL ? *flag : SIXEL_LOOP_AUTO;
9,236!
1476
        status = SIXEL_OK;
9,236✔
1477
        break;
9,236✔
1478
    case SIXEL_LOADER_OPTION_INSECURE:
5,578✔
1479
        flag = (int const *)value;
9,236✔
1480
        loader->finsecure = flag != NULL ? *flag : 0;
9,236!
1481
        status = SIXEL_OK;
9,236✔
1482
        break;
9,236✔
1483
    case SIXEL_LOADER_OPTION_START_FRAME_NO:
2,828✔
1484
        if (value == NULL) {
4,742✔
1485
            loader->has_start_frame_no = 0;
4,646✔
1486
            loader->start_frame_no = INT_MIN;
4,646✔
1487
        } else {
1,845✔
1488
            flag = (int const *)value;
96✔
1489
            loader->start_frame_no = *flag;
96✔
1490
            loader->has_start_frame_no = 1;
96✔
1491
        }
1492
        status = SIXEL_OK;
1,914✔
1493
        break;
1,914✔
1494
    case SIXEL_LOADER_OPTION_CANCEL_FLAG:
2,828✔
1495
        loader->cancel_flag = (int const *)value;
4,742✔
1496
        status = SIXEL_OK;
4,742✔
1497
        break;
4,742✔
1498
    case SIXEL_LOADER_OPTION_LOADER_ORDER:
2,828✔
1499
        allocator = loader->allocator;
4,742✔
1500
        sixel_allocator_free(allocator, loader->loader_order);
4,742✔
1501
        loader->loader_order = NULL;
4,742✔
1502
        if (value != NULL) {
4,742✔
1503
            order = (char const *)value;
1,226✔
1504
            copy = loader_strdup(order, allocator);
1,226✔
1505
            if (copy == NULL) {
1,226!
1506
                sixel_helper_set_additional_message(
×
1507
                    "sixel_loader_setopt: loader_strdup() failed.");
1508
                status = SIXEL_BAD_ALLOCATION;
×
1509
                goto end;
×
1510
            }
1511
            loader->loader_order = copy;
1,226✔
1512
        }
622✔
1513
        status = SIXEL_OK;
1,914✔
1514
        break;
1,914✔
1515
    case SIXEL_LOADER_OPTION_CONTEXT:
5,578✔
1516
        loader->context = (void *)value;
9,236✔
1517
        status = SIXEL_OK;
9,236✔
1518
        break;
9,236✔
1519
    default:
1520
        sixel_helper_set_additional_message(
×
1521
            "sixel_loader_setopt: unknown option.");
1522
        status = SIXEL_BAD_ARGUMENT;
×
1523
        goto end;
×
1524
    }
29,604✔
1525

1526
end:
44,780✔
1527
    sixel_loader_unref(loader);
74,384✔
1528

1529
end0:
44,780✔
1530
    return status;
103,988✔
1531
}
29,604✔
1532

1533
SIXELAPI char const *
1534
sixel_loader_get_last_success_name(sixel_loader_t const *loader)
8,708✔
1535
{
1536
    if (loader == NULL || loader->last_loader_name[0] == '\0') {
8,708!
1537
        return NULL;
1538
    }
1539
    return loader->last_loader_name;
8,708✔
1540
}
3,482✔
1541

1542
SIXELAPI char const *
1543
sixel_loader_get_last_source_path(sixel_loader_t const *loader)
8,535✔
1544
{
1545
    if (loader == NULL || loader->last_source_path[0] == '\0') {
8,535!
1546
        return NULL;
68✔
1547
    }
1548
    return loader->last_source_path;
8,362✔
1549
}
3,414✔
1550

1551
SIXELAPI size_t
1552
sixel_loader_get_last_input_bytes(sixel_loader_t const *loader)
4,354✔
1553
{
1554
    if (loader == NULL) {
4,354!
1555
        return 0u;
1556
    }
1557
    return loader->last_input_bytes;
4,354✔
1558
}
1,741✔
1559

1560
int
1561
sixel_loader_get_start_frame_no(sixel_loader_t const *loader,
×
1562
                                int *start_frame_no)
1563
{
1564
    if (start_frame_no != NULL) {
×
1565
        *start_frame_no = INT_MIN;
×
1566
    }
1567
    if (loader == NULL || start_frame_no == NULL ||
×
1568
        loader->has_start_frame_no == 0) {
×
1569
        return 0;
1570
    }
1571

1572
    *start_frame_no = loader->start_frame_no;
×
1573
    return 1;
×
1574
}
1575

1576
static SIXELSTATUS
1577
loader_manager_configure_component(sixel_loader_component_t *component,
9,294✔
1578
                                   void *context)
1579
{
1580
    sixel_loader_component_option_context_t *options;
9,294✔
1581

1582
    options = (sixel_loader_component_option_context_t *)context;
9,294✔
1583
    if (options == NULL || options->loader == NULL) {
9,294!
1584
        sixel_helper_set_additional_message(
×
1585
            "loader_manager_configure_component: invalid context.");
1586
        return SIXEL_BAD_ARGUMENT;
×
1587
    }
1588

1589
    return loader_apply_component_options(component,
12,986✔
1590
                                          options->loader,
3,692✔
1591
                                          options->reqcolors);
3,692✔
1592
}
3,692✔
1593

1594
static void
1595
loader_manager_trace_try_callback(char const *name, void *context)
9,294✔
1596
{
1597
    sixel_loader_manager_trace_context_t *trace;
9,294✔
1598

1599
    trace = (sixel_loader_manager_trace_context_t *)context;
9,294✔
1600
    if (trace == NULL || trace->loader == NULL) {
9,294!
1601
        return;
1602
    }
1603

1604
    trace->loader->log_input_bytes = trace->input_bytes;
9,294✔
1605
    if (name != NULL) {
9,294!
1606
        (void)sixel_compat_snprintf(trace->loader->log_loader_name,
12,986✔
1607
                                    sizeof(trace->loader->log_loader_name),
1608
                                    "%s",
1609
                                    name);
3,692✔
1610
    } else {
3,692✔
1611
        trace->loader->log_loader_name[0] = '\0';
×
1612
    }
1613
    loader_trace_try(name);
9,294✔
1614
}
3,692!
1615

1616
static void
1617
loader_manager_trace_result_callback(char const *name,
9,294✔
1618
                                     SIXELSTATUS status,
1619
                                     void *context)
1620
{
1621
    (void)context;
9,294✔
1622
    loader_trace_result(name, status);
9,294✔
1623
}
9,294✔
1624

1625
SIXELAPI SIXELSTATUS
1626
sixel_loader_load_file(
9,236✔
1627
    sixel_loader_t         /* in */ *loader,
1628
    char const             /* in */ *filename,
1629
    sixel_load_image_function /* in */ fn_load)
1630
{
1631
    SIXELSTATUS status = SIXEL_FALSE;
9,236✔
1632
    sixel_chunk_t *pchunk;
9,236✔
1633
    sixel_loader_entry_t const **plan;
9,236✔
1634
    sixel_loader_entry_t const *entries;
9,236✔
1635
    sixel_loader_factory_t *factory;
9,236✔
1636
    sixel_loader_manager_t *manager;
9,236✔
1637
    sixel_loader_chain_t *chain;
9,236✔
1638
    size_t entry_count;
9,236✔
1639
    size_t plan_length;
9,236✔
1640
    int reqcolors;
9,236✔
1641
    char const *order_override;
9,236✔
1642
    char const *env_order;
9,236✔
1643
    char const *selected_name;
9,236✔
1644
    sixel_loader_callback_state_t callback_state;
9,236✔
1645
    sixel_loader_component_option_context_t option_context;
9,236✔
1646
    sixel_loader_manager_trace_context_t trace_context;
9,236✔
1647

1648
    pchunk = NULL;
9,236✔
1649
    plan = NULL;
9,236✔
1650
    entries = NULL;
9,236✔
1651
    factory = NULL;
9,236✔
1652
    manager = NULL;
9,236✔
1653
    chain = NULL;
9,236✔
1654
    entry_count = 0;
9,236✔
1655
    plan_length = 0;
9,236✔
1656
    reqcolors = 0;
9,236✔
1657
    order_override = NULL;
9,236✔
1658
    env_order = NULL;
9,236✔
1659
    selected_name = NULL;
9,236✔
1660
    memset(&option_context, 0, sizeof(option_context));
9,236!
1661
    memset(&trace_context, 0, sizeof(trace_context));
9,236✔
1662

1663
    if (loader == NULL) {
9,236!
1664
        sixel_helper_set_additional_message(
×
1665
            "sixel_loader_load_file: loader is null.");
1666
        status = SIXEL_BAD_ARGUMENT;
×
1667
        goto end0;
×
1668
    }
1669

1670
    sixel_loader_ref(loader);
9,236✔
1671

1672
    loader->log_loader_finished = 0;
9,236✔
1673
    loader->log_loader_name[0] = '\0';
9,236✔
1674
    loader->log_input_bytes = 0u;
9,236✔
1675
    loader->log_path[0] = '\0';
9,236✔
1676
    if (filename != NULL) {
9,236✔
1677
        (void)sixel_compat_snprintf(loader->log_path,
12,669✔
1678
                                    sizeof(loader->log_path),
1679
                                    "%s",
1680
                                    filename);
3,598✔
1681
    }
3,598✔
1682
    loader_log_stage(loader, "start", "path=%s", loader->log_path);
9,236✔
1683

1684
    memset(&callback_state, 0, sizeof(callback_state));
9,236✔
1685
    callback_state.loader = loader;
9,236✔
1686
    callback_state.fn = fn_load;
9,236✔
1687
    callback_state.context = loader->context;
9,236✔
1688
    loader->callback_failed = 0;
9,236✔
1689

1690
    status = loader_factory_get_default(&factory);
9,236✔
1691
    if (SIXEL_FAILED(status)) {
9,236!
1692
        goto end;
×
1693
    }
1694
    entry_count = loader_factory_get_entries(factory, &entries);
9,236✔
1695

1696
    reqcolors = loader->reqcolors;
9,236✔
1697
    if (reqcolors > SIXEL_PALETTE_MAX) {
9,236!
1698
        reqcolors = SIXEL_PALETTE_MAX;
1699
    }
1700

1701
    status = sixel_chunk_new(&pchunk,
9,236✔
1702
                             filename,
3,658✔
1703
                             loader->finsecure,
3,658✔
1704
                             loader->cancel_flag,
3,658✔
1705
                             loader->allocator);
3,658✔
1706
    if (status != SIXEL_OK) {
9,236!
1707
        goto end;
28✔
1708
    }
1709

1710
    if (pchunk->size == 0 || (pchunk->size == 1 && *pchunk->buffer == '\n')) {
9,208!
1711
        status = SIXEL_OK;
×
1712
        goto end;
×
1713
    }
1714

1715
    if (pchunk->source_path != NULL && pchunk->source_path[0] != '\0') {
9,208!
1716
        (void)sixel_compat_snprintf(loader->log_path,
12,594✔
1717
                                    sizeof(loader->log_path),
1718
                                    "%s",
1719
                                    pchunk->source_path);
3,570✔
1720
    }
3,570✔
1721

1722
    if (pchunk->buffer == NULL || pchunk->max_size == 0) {
9,208!
1723
        status = SIXEL_LOGIC_ERROR;
×
1724
        goto end;
×
1725
    }
1726

1727
    status = SIXEL_FALSE;
9,208✔
1728
    order_override = loader->loader_order;
9,208✔
1729
    if (order_override == NULL) {
9,208✔
1730
        env_order = sixel_compat_getenv("SIXEL_LOADER_PRIORITY_LIST");
7,982✔
1731
        if (env_order != NULL && env_order[0] != '\0') {
7,982!
1732
            order_override = env_order;
5,570✔
1733
        }
4✔
1734
    }
3,020✔
1735

1736
    loader_apply_loader_suboptions(order_override);
9,208✔
1737

1738
    plan = sixel_allocator_malloc(loader->allocator,
16,154✔
1739
                                  entry_count * sizeof(*plan));
6,946✔
1740
    if (plan == NULL) {
9,208!
1741
        status = SIXEL_BAD_ALLOCATION;
×
1742
        goto end;
×
1743
    }
1744

1745
    plan_length = loader_build_plan(order_override,
12,850✔
1746
                                    entries,
3,642✔
1747
                                    entry_count,
3,642✔
1748
                                    plan,
3,642✔
1749
                                    entry_count);
3,642✔
1750
    if (plan_length == 0u) {
9,208!
1751
        if (order_override != NULL && order_override[0] != '\0') {
×
1752
            sixel_helper_set_additional_message(
×
1753
                "sixel_loader_load_file: no supported loader in loader "
1754
                "order.");
1755
            status = SIXEL_BAD_ARGUMENT;
×
1756
        } else {
1757
            sixel_helper_set_additional_message(
×
1758
                "sixel_loader_load_file: no available loader backend.");
1759
            status = SIXEL_LOADER_FAILED;
×
1760
        }
1761
        goto end;
×
1762
    }
1763

1764
    status = loader_manager_get_default(&manager);
9,208✔
1765
    if (SIXEL_FAILED(status)) {
9,208!
1766
        goto end;
×
1767
    }
1768

1769
    status = loader_manager_build_chain_from_plan(manager,
12,850✔
1770
                                                  plan,
3,642✔
1771
                                                  plan_length,
3,642✔
1772
                                                  pchunk,
3,642✔
1773
                                                  loader->allocator,
3,642✔
1774
                                                  &chain);
1775
    if (SIXEL_FAILED(status)) {
9,208!
1776
        goto end;
×
1777
    }
1778

1779
    option_context.loader = loader;
9,208✔
1780
    option_context.reqcolors = reqcolors;
9,208✔
1781
    trace_context.loader = loader;
9,208✔
1782
    trace_context.input_bytes = pchunk->size;
9,208✔
1783
    status = loader_manager_execute_chain(
9,208✔
1784
        manager,
3,642✔
1785
        chain,
3,642✔
1786
        pchunk,
3,642✔
1787
        loader_callback_trampoline,
1788
        &callback_state,
1789
        loader_manager_configure_component,
1790
        &option_context,
1791
        loader_manager_trace_try_callback,
1792
        loader_manager_trace_result_callback,
1793
        &trace_context,
1794
        &selected_name);
1795

1796
    if (SIXEL_FAILED(status)) {
9,208✔
1797
        if (status == SIXEL_FALSE) {
206!
1798
            if (!loader->callback_failed && pchunk != NULL) {
22!
1799
                status = SIXEL_LOADER_FAILED;
22✔
1800
                loader_publish_diagnostic(pchunk, filename);
22✔
1801
            } else {
22✔
1802
                sixel_helper_set_additional_message(
×
1803
                    "sixel_loader_load_file: loader returned "
1804
                    "unspecified failure.");
1805
                status = SIXEL_LOADER_FAILED;
×
1806
            }
1807
        }
22✔
1808
        goto end;
206✔
1809
    }
1810

1811
    if (selected_name != NULL) {
9,002!
1812
        (void)sixel_compat_snprintf(loader->last_loader_name,
12,543✔
1813
                                    sizeof(loader->last_loader_name),
1814
                                    "%s",
1815
                                    selected_name);
3,541✔
1816
    } else {
3,541✔
1817
        loader->last_loader_name[0] = '\0';
×
1818
    }
1819
    loader->last_input_bytes = pchunk->size;
9,002✔
1820
    if (pchunk->source_path != NULL) {
12,543✔
1821
        size_t path_len;
8,829✔
1822

1823
        path_len = strlen(pchunk->source_path);
8,829✔
1824
        if (path_len >= sizeof(loader->last_source_path)) {
8,829!
1825
            path_len = sizeof(loader->last_source_path) - 1u;
1826
        }
1827
        memcpy(loader->last_source_path, pchunk->source_path, path_len);
8,829✔
1828
        loader->last_source_path[path_len] = '\0';
8,829✔
1829
    } else {
3,473✔
1830
        loader->last_source_path[0] = '\0';
173✔
1831
    }
1832

1833
end:
5,578✔
1834
    loader_chain_unref(chain);
9,236✔
1835
    chain = NULL;
9,236✔
1836
    loader_manager_unref(manager);
9,236✔
1837
    manager = NULL;
9,236✔
1838
    if (plan != NULL) {
9,236!
1839
        sixel_allocator_free(loader->allocator, plan);
9,208✔
1840
        plan = NULL;
9,208✔
1841
    }
3,642✔
1842
    loader_factory_unref(factory);
9,236✔
1843
    factory = NULL;
9,236✔
1844
    sixel_chunk_destroy(pchunk);
9,236✔
1845
    sixel_loader_unref(loader);
9,236✔
1846

1847
end0:
5,578✔
1848
    return status;
12,894✔
1849
}
3,658✔
1850

1851
/* load image from file */
1852

1853
SIXELAPI SIXELSTATUS
1854
sixel_helper_load_image_file(
×
1855
    char const                /* in */     *filename,     /* source file name */
1856
    int                       /* in */     fstatic,       /* whether to */
1857
                                                             /* extract a */
1858
                                                             /* static image */
1859
                                                             /* from an */
1860
                                                             /* animated gif */
1861
    int                       /* in */     fuse_palette,  /* whether to */
1862
                                                             /* use a */
1863
                                                             /* paletted */
1864
                                                             /* image; set */
1865
                                                             /* non-zero to */
1866
                                                             /* request one */
1867
    int                       /* in */     reqcolors,     /* requested */
1868
                                                             /* number of */
1869
                                                             /* colors; */
1870
                                                             /* should be */
1871
                                                             /* equal to or */
1872
                                                             /* less than */
1873
                                                             /* SIXEL_ */
1874
                                                             /* PALETTE_ */
1875
                                                             /* MAX */
1876
    unsigned char             /* in */     *bgcolor,      /* background */
1877
                                                             /* color, may */
1878
                                                             /* be NULL */
1879
    int                       /* in */     loop_control,  /* one of enum */
1880
                                                             /* loopControl */
1881
    sixel_load_image_function /* in */     fn_load,       /* callback */
1882
    int                       /* in */     finsecure,     /* true if do */
1883
                                                             /* not verify */
1884
                                                             /* SSL */
1885
    int const                 /* in */     *cancel_flag,  /* cancel flag, */
1886
                                                             /* may be */
1887
                                                             /* NULL */
1888
    void                      /* in/out */ *context,      /* private data */
1889
                                                             /* passed to */
1890
                                                             /* callback */
1891
                                                             /* function, */
1892
                                                             /* may be */
1893
                                                             /* NULL */
1894
    sixel_allocator_t         /* in */     *allocator     /* allocator */
1895
                                                             /* object, */
1896
                                                             /* may be */
1897
                                                             /* NULL */
1898
)
1899
{
1900
    SIXELSTATUS status = SIXEL_FALSE;
×
1901
    sixel_loader_t *loader;
×
1902

1903
    loader = NULL;
×
1904

1905
    status = sixel_loader_new(&loader, allocator);
×
1906
    if (SIXEL_FAILED(status)) {
×
1907
        goto end;
×
1908
    }
1909

1910
    status = sixel_loader_setopt(loader,
×
1911
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
1912
                                 &fstatic);
1913
    if (SIXEL_FAILED(status)) {
×
1914
        goto end;
×
1915
    }
1916

1917
    status = sixel_loader_setopt(loader,
×
1918
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
1919
                                 &fuse_palette);
1920
    if (SIXEL_FAILED(status)) {
×
1921
        goto end;
×
1922
    }
1923

1924
    status = sixel_loader_setopt(loader,
×
1925
                                 SIXEL_LOADER_OPTION_REQCOLORS,
1926
                                 &reqcolors);
1927
    if (SIXEL_FAILED(status)) {
×
1928
        goto end;
×
1929
    }
1930

1931
    status = sixel_loader_setopt(loader,
×
1932
                                 SIXEL_LOADER_OPTION_BGCOLOR,
1933
                                 bgcolor);
1934
    if (SIXEL_FAILED(status)) {
×
1935
        goto end;
×
1936
    }
1937

1938
    status = sixel_loader_setopt(loader,
×
1939
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
1940
                                 &loop_control);
1941
    if (SIXEL_FAILED(status)) {
×
1942
        goto end;
×
1943
    }
1944

1945
    status = sixel_loader_setopt(loader,
×
1946
                                 SIXEL_LOADER_OPTION_INSECURE,
1947
                                 &finsecure);
1948
    if (SIXEL_FAILED(status)) {
×
1949
        goto end;
×
1950
    }
1951

1952
    status = sixel_loader_setopt(loader,
×
1953
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
1954
                                 cancel_flag);
1955
    if (SIXEL_FAILED(status)) {
×
1956
        goto end;
×
1957
    }
1958

1959
    status = sixel_loader_setopt(loader,
×
1960
                                 SIXEL_LOADER_OPTION_CONTEXT,
1961
                                 context);
1962
    if (SIXEL_FAILED(status)) {
×
1963
        goto end;
×
1964
    }
1965

1966
    status = sixel_loader_load_file(loader, filename, fn_load);
×
1967

1968
end:
1969
    sixel_loader_unref(loader);
×
1970

1971
    return status;
×
1972
}
1973

1974

1975
SIXELAPI size_t
1976
sixel_helper_get_available_loader_names(char const **names, size_t max_names)
88✔
1977
{
1978
    sixel_loader_entry_t const *entries;
88✔
1979
    sixel_loader_factory_t *factory;
88✔
1980
    size_t entry_count;
88✔
1981
    size_t limit;
88✔
1982
    size_t index;
88✔
1983
    SIXELSTATUS status;
88✔
1984

1985
    entries = NULL;
88✔
1986
    factory = NULL;
88✔
1987
    entry_count = 0u;
88✔
1988
    limit = 0u;
88✔
1989
    index = 0u;
88✔
1990
    status = SIXEL_FALSE;
88✔
1991

1992
    status = loader_factory_get_default(&factory);
88✔
1993
    if (SIXEL_FAILED(status)) {
88!
1994
        return 0u;
1995
    }
1996
    entry_count = loader_factory_get_entries(factory, &entries);
88✔
1997

1998
    if (names != NULL && max_names > 0) {
88!
1999
        limit = entry_count;
44✔
2000
        if (limit > max_names) {
44!
2001
            limit = max_names;
2002
        }
2003
        for (index = 0; index < limit; ++index) {
188✔
2004
            names[index] = entries[index].name;
144✔
2005
        }
88✔
2006
    }
16✔
2007

2008
    loader_factory_unref(factory);
88✔
2009

2010
    return entry_count;
88✔
2011
}
32✔
2012

2013

2014
/* emacs Local Variables:      */
2015
/* emacs mode: c               */
2016
/* emacs tab-width: 4          */
2017
/* emacs indent-tabs-mode: nil */
2018
/* emacs c-basic-offset: 4     */
2019
/* emacs End:                  */
2020
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2021
/* 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